• 締切済み

Java 可変長引数と優先度

Java 可変長引数と優先度  こんにちは。c#初心者兼、java始めました です。  可変長引数の場合のオーバーロードの優先度について困っています(質問と言うより半分愚痴です)。  javaにも可変長引数ってありますよね。(ジェネリックの弱さにイライラしていたけれど)「javaも捨てたものじゃない」と感心しながら使っていると、いきなりコンパイルエラー。  sampleMethod(10, 20); と書いている部分でエラーが発生していました。  自分のメソッド定義を確認しみると、  void sampleMethod(int...);  void sampleMethod(Object...);  の2種類のオーバーロードがあり、ambiguousなため判別不能らしいです。    確かに、AutoBoxingされれば、int...だけでなく、Object...にもマッチしますが、どう考えてもBoxingしない方が優先度が高いはずです。というか、高く設定されるべきです。  個人的に基準にしているc#ではこのようなことは起きませんでした。  (あ、やっぱり捨てたものかもしれない ←心変わり早す(ry )    試しにc#でInteger型のクラスを作り、(実際はタブーですが)暗黙のキャストを双方向でオーバーロードして実験してみました。   public class Integer // ここからc# {   private int _value;   //----------------------------   public Integer(int value) { _value = value; }   //----------------------------   // Integer → int の暗黙の型変換   public static implicit operator int(Integer value)   { return value._value; }   // int → Integer の暗黙の型変換   public static implicit operator Integer(int value)   { return new Integer(value); }   //----------------------------   // javaの sampleMethod(int... values)に相当   public static void SampleMethod(params int[] values) { }   // javaの sampleMethod(Object... values)に相当   public static void SampleMethod(params Object[] values) { } }  そして、Integer.SampleMethod(10, 20); と書いてみると……問題……なし。  ちゃんと、SampleMethod(param int[] values)が選ばれていました。  つまり、(個人的には)java(コンパイラ)の方が不可解な動きをしているのです。  sampleMethod(int, int)とsampleMethod(Object, Object)があるときは問題ないので、可変長引数のときだけambiguousになるようです。  「それなら」と思い、sampleMethod(int, int...)とsampleMethod(Object, Object...)を作ってみましたが、やはりエラーが出ます。  何か良い解決策はあるでしょうか?  可変長じゃない引数のオーバーロードを大量に作ることと、片方の可変長をやめること以外でアドバイスをお願いします。  もしくは、「個人的には~だから、パクリのc#よりjavaの方が動きが正しいぞ!」という方がいらっしゃいましたら、ご意見ください。

  • Java
  • 回答数5
  • ありがとう数15

みんなの回答

  • sech1152
  • ベストアンサー率72% (45/62)
回答No.5

No.2です。 気になったので、あれからも少し調べてみました。 結論としては、No.4さんの回答にあるように“仕様として納得するしかない”ということになりそうです。 解決策としては、No.3さんが挙げている“Integer...を使う”や“先に配列にしてしまう”の他に、今回の質問の前提から崩れてしまうかもしれませんが、どのメソッドが呼ばれるのかは実行時ではなくコンパイル時に決定されますので、メソッド名を分けてしまう(オーバーロードをやめてしまう)というのも、実務上では現実的な選択肢の一つなのかも知れません。 以下、調べた中で、この件に関係しそうなページを紹介しておきます。 “int...”と“Integer...”のオーバーロードでambiguousになってしまうというQ&A(英語です) http://stackoverflow.com/questions/14053596/compiler-error-reference-to-call-ambiguous Java6だとエラーにならないという話 http://d.hatena.ne.jp/clomie_p/20121214/1355504323 公式のドキュメントにも、理由は違うみたいですが“可変長引数のオーバーロードは避けてください”という記述がありました。 (具体的な文脈(理由)は、リンク先で確認してください) http://docs.oracle.com/javase/jp/7/technotes/guides/language/varargs.html このページの一番下。 「一般的に、可変長引数メソッドはオーバーロードしないようにしてください。」 http://otn.oracle.co.jp/technology/global/jp/sdn/java/private/techtips/2005/tt0104.html#2 ここの少し上の部分。 「したがって、オーバーロードされたメソッドには可変引数を使用しないようにしてください。」 「・・・、オートボックス化を行わないようにすることが重要である場合には、可変引数を使用するメソッドはできるだけ避けてください。」 言語仕様として関係しそうなところは、15.12.2(特に15.12.2.4と15.12.2.5)だと思います。 http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.4 (英語です)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

ちょろっと調べてみましたが, 本件に関しては「Java の仕様」としか言いようがありません. より正確には「現在の挙動が正解であり, 古いバージョンでは『間違って』通してしまっていた」ということになるかと思います. 参考としては http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6886431 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7044388 あたり. 実際, 仕様をななめ読みした限りでは可変長引数や自動ボクシングが導入された (注: こいつらは同時に導入されています) バージョン5 以降でこの辺は変わっていません. ただ, 古いバージョンではいろいろとオーバーロードの解決にバグ (= 仕様と異なる動作) を抱えていたようで, 仕様に合わせて動作を「直した」のが現在の状態だと思います. ということで, 「根本的な解決策」は存在しません. しいて言えば「オラクルに『やっぱりこの仕様は問題があるようだから変えるか』と思わせるほど強力にプッシュする」くらいでしょうか.

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

とりあえず, エラーメッセージは「ambiguousなため判別不能らしい」とかではなくコンパイラの出したものをそのまま見せてほしい. でこの手の話題だと本当は「言語仕様を読め」と書きたいわけだが, 実際に読んでもよくわからなかった (苦笑). のでそうは書かずに読んで (あと #2 の実験から) 分かったことを書いておく. 以下断定調で書きますが「たぶんそんなところ」というニュアンスは適宜入れてください. 結論から言うと, これは「Java の仕様」です. ただし, なぜこのような仕様にしているのかはわかりません. Java において可変長引数メソッドは構文糖であり, 例えば 可変長引数メソッド aMethod(int... args) とそれに対する呼び出し aMethod(1, 2, 3) はそれぞれ aMethod(int[] args) とそれに対する呼び出し aMethod(new int[]{1, 2, 3}) と処理されます. そして, 「呼び出すことのできるメソッド」を見つける際, 可変長引数メソッドについては暗黙の変換やボクシング・アンボクシングも考慮して探します. つまり, 質問に挙がっている例だと ・sampleMethod(int... args) (= sampleMethod(int[] args)) に対する呼び出し sampleMethod(10, 20) (= sampleMethod(new int[]{10, 20})) ・sampleMethod(Object... args) (= sampleMethod(Object[] args)) に対する呼び出し sampleMethod(10, 20) (= sampleMethod(new Object[]{10, 20})) の両方が候補に挙がります. そして, 複数の候補がある場合には仮引数の型を見て「どちらがより限定的であるか」を判断します. ところが, 今の場合それぞれの仮引数の型である int... = int[] と Object... = Object[] とではどちらとも「より限定的である」とはいえません (どちら向きにも代入互換性がないことに注意). したがって「一方が他方に比べてより限定的である」ということができないので, どちらを呼び出していいかわからずエラーとしています. 一方 #2 のように int... ではなく Integer... を使うと, Integer... = Integer[] から Object... = Object[] への代入が可能なので Integer... の方が「より限定的である」と判断され, Integer... の呼び出しと解釈されます. 元の質問文に戻って ・sampleMethod(int, int)とsampleMethod(Object, Object)があるときは問題ない ・sampleMethod(int, int...)とsampleMethod(Object, Object...)を作ってみましたが、やはりエラーが出ます も, 同様に言語仕様から判定することができます (前者はどちらも固定長引数メソッドなので「ボクシング・アンボクシングしないものが優先」という規定による, 後者は結局可変長引数メソッドになってしまって上と同じ). 解決策は.... ・Integer... にする (#2 とおなじ) ・コンパイラに任せない: 例えば, 引数を new int[]{10, 20} にすると int... の方を呼び出します くらいしか思いつかない. ちなみにですが, 可変長引数メソッド aMethod(Object... args) は固定長引数メソッド aMethod(Object[] args) とも解釈されるので, aMethod(Object arg) aMethod(Object... args) というオーバーロードがあると aMethod(null) は後者のメソッドを呼び出します (と使用に書いてある).

koumei000
質問者

お礼

 回答ありがとうございます。 > コンパイラが出したものをそのまま…  The method sampleMethod(Object[]) is ambiguous for the type SampleClass  「メソッド sampleMethod(Obhect[])は型 SampleClassにとって不明瞭です」  がエラー文です。 > コンパイラに任せない:例えば…  その方法も最初に考えたのですが、それでは結局、使用側の手間が増えてしまうので微妙です。  javaの仕様なら、仕様がないのですが、古いコンパイラで通っていたものを(多分)AutoBoxingが追加されたせいでエラーを出してしまうのは頂けないですね。  (実際に同じコードなのに、学校PCの古いコンパイラなら通るのですが、マイPCのコンパイラでは、ご丁寧にきっちりエラー出してくれます)

  • sech1152
  • ベストアンサー率72% (45/62)
回答No.2

試してみたら、確かに、sampleMethod(10, 20);の部分でコンパイルエラーが出ますね。 いろいろやってみたのですが、 sampleMethod(int...) ではなく sampleMethod(Integer...) にするとエラーが発生しなくなりました。 // テストコード public class Test { public static void main(String[] args) { Test t = new Test(); t.sampleMethod(10, 20); // Integer... t.sampleMethod("a", "b"); // Object... Integer[] a = {1, 2, 3}; t.sampleMethod(a); // Integer... int[] b = {1, 2, 3}; t.sampleMethod(b); // Object... } void sampleMethod(Integer... i) { System.out.println("Integer..." + i.length); } void sampleMethod(Object... o) { System.out.println("Object..." + o.length); } } // テストコード ここまで // 結果 Integer...2 Object...2 Integer...3 Object...1 // 結果 ここまで それにしても、 void sampleMethod(int) と void sampleMethod(Object) のオーバーロードならエラーにならないのに、何でですかね? (Java7 + Eclipse4.2で試しました。)

koumei000
質問者

お礼

 回答、検証ありがとうございました。  前述の通り、何故か可変長引数の場合のみ問題があるようです。十中八九、IntegerへのAutoBoxingが優先されているのが原因でしょうが、不可解ですよね。  パフォーマンスと関係のない場所ならsampleMethod(Integer... values)を用意してもいいのですが、パフォーマンスに響く部分なら考え物ですね。  何はともあれ、ありがとうございました。

  • wormhole
  • ベストアンサー率28% (1622/5658)
回答No.1

> 自分のメソッド定義を確認しみると、 > void sampleMethod(int...); > void sampleMethod(Object...); > の2種類のオーバーロードがあり、ambiguousなため判別不能らしいです。 これは、 sampleMethod(); のように引数なしのときどちらを呼べばいいのかわかりませんから。 >「それなら」と思い、sampleMethod(int, int...)とsampleMethod(Object, Object...)を作ってみましたが、やはりエラーが出ます。 メソッド定義ではエラー起きませんが、どういう呼び出し方でどういうエラーが出てるのですか?

koumei000
質問者

補足

> sampleMethod(); > のように引数なしのときどちらを > 呼べばいいのかわかりませんから。  それは当然です。c#でもエラー出ます。 void sampleMethod(); を定義すれば解決ですが。 > メソッド定義ではエラー起きませんが、…  前述(質問文)のとおり、sampleMethod(10, 20);の部分でエラーが出ます。

関連するQ&A

  • Javaで可変長メソッドから別の可変長を呼ぶには?

    Javaで以下のような二つの可変長メソッドがあるとして func1からfunc2を呼び出すにはどういう風にかけばいいのでしょうか? 可変長パラメーターを持つメソッドの書き方はググるといっぱい見つかるのですが 可変長から可変長を呼び出す方法がわかりませんでした。 public static String func1(Object...args){ } public static String func2(Object...args){ } ちなみにどちらも自作のメソッドで 処理をパーツ化するために別々のメソッドに分けたいです。 (それぞれ単独で使うこともあるので) 以上よろしくお願いいたします。

    • ベストアンサー
    • Java
  • C++のクラスの可変引数化を禁止する方法。

    C++のクラスの可変引数化を禁止する方法。 クラスのインスタンスをprintfの引数にするのをコンパイル・エラーにする方法はないでしょうか? コピーコンストラクタや代入のオーバーロードをprivateにしてもエラーが出ないので方法を探しています。 #include "stdafx.h" class CTest { private: int intdata; public: CTest() : intdata(0) { }; private: void operator =(const CTest& src) {} CTest(const CTest& src) {} }; int _tmain(int argc, _TCHAR* argv[]) { CTest ctest; printf( "%s %p\n", ctest, ctest ); return 0; }

  • 引数の可変

    覚えたての引数の可変を使ってみた素人であります。(独学です 見よう見真似で書いた為、目茶苦茶だと思われます。 自分で簡単なexeファイルを作ってみていて 引数の可変が必要となったので付け足そうとしていたのですが、「引数の可変」部の他 意外にもfor文からもエラーが出ました(while使えば済む話なのですが) 今までfor文をあまり使わずwhile文ばかり使っていた為、for文の基本的な構文が失敗しているのかもしれません。折角だから使ってみようとした結果がこれです。 #include <stdio.h> #include <locale.h> #include "DxLib.h" #include <string.h> #include <stdarg.h> int i; ・・・略・・・ class CharsOut{ ____int ypixel; ____char *hangar; ____int size; ____int HowMany; ____int list; public:   ____int CharsOuting(int HowMany,int ypixel,int size,...); }; int CharsOuting(int HowMany,int ypixel,int size,...){ ____va_start(list,HowMany); ____for (i=0;i<HowMany;i++){ ________printf("%s",va_arg(list,char *)); ____} ____va_end(list); ____return 0; } ypixel、size...といった変数等は気にしないでください。 成功していたらコードを足すつもりでした。 自分がやりたいことは、main関数から変数HowMany,*hangar等を受け取り 文字列*hangarを引数の分出力したいのであります。引数の可変は*hangarを何個か渡すつもりだったので使用しました。 こんな感じに使うつもりでしたc.CharsOuting(2,0,32,"I am ", "hangury"); ※リストは(int HowMany,int ypixel,int size,...)です ※ハングリーのスペルが違う等ということはとてつもなくどうでもいいことなのです。 自分で「main関数から*hangarを受け取り」と書いておきながらリストに*hangarがない時点でおかしいと思ってはいるのですが、記述の方法が分からないので書けません。 以下エラー (名前とか一部改変・不要な部分省略 1>------ ビルド開始: プロジェクト:(≡ω≡.), 構成: Debug Win32 ------ 1>コンパイルしています... 1>soucer.cpp 1>error C2040: 'i' : 'int' は 'int [5]' と間接操作のレベルが異なります。 1>error C2065: 'list' : 定義されていない識別子です。 1>error C2440: '=' : 'int' から 'int [5]' に変換できません。 1>配列型への変換はありませんが、参照またはポインタから配列への変換があります。 1>error C2446: '<' : 'int' 型から 'int *' 型への変換ができません。 1>整数型からポインタ型への変換には reinterpret_cast、C スタイル キャストまたは関数スタイル キャストが必要です。 1>error C2040: '<' : 'int [5]' は 'int' と間接操作のレベルが異なります。 1>error C2105: '++' には左辺値が必要です。 1>error C2065: 'list' : 定義されていない識別子です。 1>error C2065: 'list' : 定義されていない識別子です。 1>ビルドログは "file://c:\・・・\Debug\BuildLog.htm" に保存されました。 1>(≡ω≡.) - エラー 8、警告 0 ========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ========== 文章能力なくて申し訳ありません。 ご回答頂ければ幸いであります。宜しくお願いします。 OS:WinXP コンパイラ:Visual C++

  • 可変長引数リストを用いたプログラムがコンパイル不可

    可変長引数リストを用いたプログラムがコンパイルできません。 【Sample.javaの内容】 class MyClass extends java.lang.Object { void meth(String ... s) { for (int i=0; i<s.length; i++) { System.out.println(s[i] + " : "); } System.out.println(); } } public class Sample extends java.lang.Object { public static void main (String args[]) { MyClass mc = new MyClass(); mc.meth(); mc.meth("what", "is", "this"); mc.meth("this", "is", "an", "example"); } } 【普通にjavacでコンパイル】 >javac Sample.java Sample.java:2: <identifier> がありません。 void meth(String ... s) { ^ Sample.java:8: <identifier> がありません。 } ^ エラー 2 個 【-sourceオプションをつけてコンパイル】 >javac -source 1.5 Sample.java javac: 1.5 は無効なソースバージョンです。 (以下javacのusageが延々と表示される) ※-version 1.6でも同様の結果でした。 【バージョン情報】 C:\>java -version java version "1.6.0_03" Java(TM) SE Runtime Environment (build 1.6.0_03-b05) Java HotSpot(TM) Client VM (build 1.6.0_03-b05, mixed mode, sharing)

    • ベストアンサー
    • Java
  • Javaのオーバーライドについて教えてください!

    //Sample.java class A extends Fina{ public int part(String str1,String str2){ int iN1 = Integer.parseInt(s1); int iN2 = Integer.parseInt(s2); intNum1 *= intNum2; return intNum1; } } このプログラムに対応するように同じクラス内でオーバーロードした際のプログラムを教えてください。 オーバーロードの内容は、partメソッドをオーバロードして引数にString型のデータを二つ受け取り、2つのString型のデータをそれぞれint型に変換し、それぞれかけた値を別のクラスでメインから呼び出すにはどうすれば良いですか?

    • ベストアンサー
    • Java
  • javaのmainの引数はなぜstring[]?

    Java初心者です。 基礎的なことなのですが、質問させてください。 javaのmainメソッド定義の public static void main (String[] args) について、String[] argsの部分がjavaコマンドで実行する際に与える引数であることは 理解できました。しかし、なぜStringの配列なのかが理解できません。 実行時の引数であれば、intやdoubleも使い道があるようにも感じるので、 プログラマの好きに定義できた方が便利な気がしますし、実行時に引数を与えないなら public static void main () としてしまったほうがわかりやすいとも感じてしまいます。 実はコンパイル時や実行時に裏の見えない部分でStringの配列を使っているとか、 何か理由があるのでしょうか? mainの定義は main (String[] args)と暗記でもいいのですが、 気になったので知りたく、質問させてください。

    • ベストアンサー
    • Java
  • Javaのプログラムについて教えて下さい

    //Sample.java class Sample void methodA(int temp) if(temp == 0) String str = new String("Exception"); Integer.parseInt(str); else if(temp == 1) int[] array = new int[temp]; else Exception exc = new Exception(); throw exc; //SampleMain.java class ExceptionMain public static void main(string[] args) Sample sam = new Sample(); Int temp = Integer.parseInt(args[0]); Sam.method(temp); } } このプログラムの場合、ExceptionMainに新たにコードを加えて、コマンドライン引数が0,1,その他の場合に例外メッセージを出力するプログラムを教えて下さい。

    • ベストアンサー
    • Java
  • Javaのプログラムについて至急教えてください!

    //Sub.java class Super{ public void A(){} } class Sub extends Super{ /* (1) */ } このプログラムの(1)に入るものを下から2つ選んで他のクラスのメインメソッドから呼び出すのですがどうやってもわかりません。わかる方教えてください! public void A(){} public String A(){ return "pool";} public int A(String str){return Integer.parseInt(st);} public int A(){return 3;} public Object A(){return new Object();}

    • ベストアンサー
    • Java
  • javaプログラムについて

    コマンドライン引数から複数の値を受け取り、それらの最大と最小を表示する、というプログラムなのですが。 class Maxmin{   public static void main (String[] args) {    int max=Integer.MIN_VALUE;    int min=Integer.MAX_VALUE;    for (int i=0; i<args.length; i++){     int num= Integer.parseInt(args[i]);     if(num>max)     max=num;     if(num<min)     min=num;    }    System.out.println("最大値は" + max + "です。");    System.out.println("最小値は" + min + "です。");  } } このプログラムでも問題なく表示されるのですが、MIN_VALUEとMAX_VALUEを使用せずに表示する事、と指摘を受けました。自分の中でぱっと思いついたのがこれだったのですが、他にはどのような方法があるのでしょうか?

    • ベストアンサー
    • Java
  • 実引数リストと仮引数リストの長さが異なります

    実引数と仮引数はあっているはずですが、次のエラーが出ます。どうしてでしょうか? ------エラー----------------------- java: クラス Itemのコンストラクタ Itemは指定された型に適用できません。 期待値: 引数がありません 検出値: java.lang.String,int,java.util.Date 理由: 実引数リストと仮引数リストの長さが異なります -------------------------------------------------- public class Main { public static void main(String[] args) { String name= "test"; int price = 0; Date date = new Date(); Item item = new Item(name,price,date);//コンストラクタ実引数 } } class Item{ String name; int price; Date date; void Item(String name, int price, Date date)//コンストラクタ仮引数 { this.name = name; this.price = price; this.date = date; } }

    • ベストアンサー
    • Java