電子書籍の厳選無料作品が豊富!

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の方が動きが正しいぞ!」という方がいらっしゃいましたら、ご意見ください。

A 回答 (5件)

No.2です。


気になったので、あれからも少し調べてみました。

結論としては、No.4さんの回答にあるように“仕様として納得するしかない”ということになりそうです。

解決策としては、No.3さんが挙げている“Integer...を使う”や“先に配列にしてしまう”の他に、今回の質問の前提から崩れてしまうかもしれませんが、どのメソッドが呼ばれるのかは実行時ではなくコンパイル時に決定されますので、メソッド名を分けてしまう(オーバーロードをやめてしまう)というのも、実務上では現実的な選択肢の一つなのかも知れません。


以下、調べた中で、この件に関係しそうなページを紹介しておきます。

“int...”と“Integer...”のオーバーロードでambiguousになってしまうというQ&A(英語です)
http://stackoverflow.com/questions/14053596/comp …

Java6だとエラーにならないという話
http://d.hatena.ne.jp/clomie_p/20121214/1355504323


公式のドキュメントにも、理由は違うみたいですが“可変長引数のオーバーロードは避けてください”という記述がありました。
(具体的な文脈(理由)は、リンク先で確認してください)

http://docs.oracle.com/javase/jp/7/technotes/gui …
このページの一番下。
「一般的に、可変長引数メソッドはオーバーロードしないようにしてください。」

http://otn.oracle.co.jp/technology/global/jp/sdn …
ここの少し上の部分。
「したがって、オーバーロードされたメソッドには可変引数を使用しないようにしてください。」
「・・・、オートボックス化を行わないようにすることが重要である場合には、可変引数を使用するメソッドはできるだけ避けてください。」


言語仕様として関係しそうなところは、15.12.2(特に15.12.2.4と15.12.2.5)だと思います。
http://docs.oracle.com/javase/specs/jls/se7/html …
(英語です)
    • good
    • 0

ちょろっと調べてみましたが, 本件に関しては「Java の仕様」としか言いようがありません. より正確には「現在の挙動が正解であり, 古いバージョンでは『間違って』通してしまっていた」ということになるかと思います.



参考としては
http://bugs.sun.com/bugdatabase/view_bug.do?bug_ …
http://bugs.sun.com/bugdatabase/view_bug.do?bug_ …
あたり.

実際, 仕様をななめ読みした限りでは可変長引数や自動ボクシングが導入された (注: こいつらは同時に導入されています) バージョン5 以降でこの辺は変わっていません. ただ, 古いバージョンではいろいろとオーバーロードの解決にバグ (= 仕様と異なる動作) を抱えていたようで, 仕様に合わせて動作を「直した」のが現在の状態だと思います.

ということで, 「根本的な解決策」は存在しません. しいて言えば「オラクルに『やっぱりこの仕様は問題があるようだから変えるか』と思わせるほど強力にプッシュする」くらいでしょうか.
    • good
    • 0

とりあえず, エラーメッセージは「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) は後者のメソッドを呼び出します (と使用に書いてある).
    • good
    • 0
この回答へのお礼

 回答ありがとうございます。

> コンパイラが出したものをそのまま…
 The method sampleMethod(Object[]) is ambiguous for the type SampleClass
 「メソッド sampleMethod(Obhect[])は型 SampleClassにとって不明瞭です」
 がエラー文です。

> コンパイラに任せない:例えば…
 その方法も最初に考えたのですが、それでは結局、使用側の手間が増えてしまうので微妙です。

 javaの仕様なら、仕様がないのですが、古いコンパイラで通っていたものを(多分)AutoBoxingが追加されたせいでエラーを出してしまうのは頂けないですね。
 (実際に同じコードなのに、学校PCの古いコンパイラなら通るのですが、マイPCのコンパイラでは、ご丁寧にきっちりエラー出してくれます)

お礼日時:2013/11/07 10:11

試してみたら、確かに、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で試しました。)
    • good
    • 0
この回答へのお礼

 回答、検証ありがとうございました。
 前述の通り、何故か可変長引数の場合のみ問題があるようです。十中八九、IntegerへのAutoBoxingが優先されているのが原因でしょうが、不可解ですよね。

 パフォーマンスと関係のない場所ならsampleMethod(Integer... values)を用意してもいいのですが、パフォーマンスに響く部分なら考え物ですね。

 何はともあれ、ありがとうございました。

お礼日時:2013/11/05 00:35

> 自分のメソッド定義を確認しみると、


> void sampleMethod(int...);
> void sampleMethod(Object...);
> の2種類のオーバーロードがあり、ambiguousなため判別不能らしいです。

これは、
sampleMethod();
のように引数なしのときどちらを呼べばいいのかわかりませんから。

>「それなら」と思い、sampleMethod(int, int...)とsampleMethod(Object, Object...)を作ってみましたが、やはりエラーが出ます。

メソッド定義ではエラー起きませんが、どういう呼び出し方でどういうエラーが出てるのですか?

この回答への補足

> sampleMethod();
> のように引数なしのときどちらを
> 呼べばいいのかわかりませんから。
 それは当然です。c#でもエラー出ます。
void sampleMethod(); を定義すれば解決ですが。

> メソッド定義ではエラー起きませんが、…
 前述(質問文)のとおり、sampleMethod(10, 20);の部分でエラーが出ます。

補足日時:2013/11/04 13:08
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!