34

次のコードを検討してください。

public class Converter {

    public <K> MyContainer<K> pack(K key, String[] values) {
        return new MyContainer<>(key);
    }

    public MyContainer<IntWrapper> pack(int key, String[] values) {
        return new MyContainer<>(new IntWrapper(key));
    }


    public static final class MyContainer<T> {
        public MyContainer(T object) { }
    }

    public static final class IntWrapper {
        public IntWrapper(int i) { }
    }


    public static void main(String[] args) {
        Converter converter = new Converter();
        MyContainer<IntWrapper> test = converter.pack(1, new String[]{"Test", "Test2"});
    }
}

上記のコードは問題なくコンパイルされます。ただし、シグネチャとの両方でを変更String[]すると、コンパイラは の呼び出しが曖昧であると警告します。String...packnew String[]{"Test", "Test2"}"Test", "Test2"converter.pack

これで、あいまいと見なされる理由が理解できます (intに自動ボックス化されるためInteger、 の条件またはその欠如に一致する可能性がありKます)。ただし、理解できないのは、のString[]代わりに使用している場合にあいまいさが存在しない理由ですString...

誰かがこの奇妙な振る舞いを説明できますか?

4

4 に答える 4

15

あなたの最初のケースはかなり簡単です。以下の方法:

public MyContainer<IntWrapper> pack(int key, Object[] values) 

は引数 - に完全に一致します(1, String[])JLS セクション 15.12.2から:

最初のフェーズ (§15.12.2.2) は、ボックス化またはボックス化解除の変換を許可せずにオーバーロードの解決を実行します。

これで、これらのパラメーターを 2 番目のメソッドに渡す際に、ボクシングは必要ありません。Object[]のスーパータイプString[]です。String[]パラメータに引数を渡すことObject[]は、Java 5 より前でも有効な呼び出しでした。


コンパイラは、2番目のケースでトリックをしているようです:

2 番目のケースでは、var-args を使用したため、その JLS セクションで説明されている 3 番目のフェーズに従って、var-args とボックス化またはボックス化解除の両方を使用して、メソッドのオーバーロードの解決が行われます。

第 3 フェーズ (§15.12.2.4) では、オーバーロードを可変アリティ メソッド、ボックス化、およびボックス化解除と組み合わせることができます。

var-argsを使用しているため、ここでは 2 番目のフェーズは適用されないことに注意してください。

2 番目のフェーズ (§15.12.2.3) では、ボックス化とボックス化解除を許可しながらオーバーロードの解決を実行しますが、可変アリティ メソッドの呼び出しは引き続き使用できません。

ここで起こっているのは、コンパイラが型引数を正しく推論していないことです* (実際には、型パラメータが仮パラメータとして使用されているため、正しく推論しています。この回答の最後にある更新を参照してください)。したがって、メソッド呼び出しの場合:

MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");

コンパイラはK、ジェネリック メソッドの の型をIntWrapperLHS から と推測する必要がありました。しかし、タイプであると推測Kしているようです。そのため、Integer両方のメソッドがこのメソッド呼び出しに等しく適用できるようになりました。var-argsboxing

ただし、そのメソッドの結果が何らかの参照に割り当てられていない場合、この場合のようにコンパイラが適切な型を推測できないことを理解できます。あいまいなエラーを与えることは完全に許容されます。

converter.pack(1, "Test", "Test2");

たぶん、一貫性を維持するために、最初のケースではあいまいとマークされています。しかし、JLSからの信頼できる情報源や、この問題について言及している他の公式の参考文献が見つからなかったため、やはりよくわかりません。検索を続けます。見つけたら、答えを更新します。


明示的な型情報でコンパイラをだましましょう。

メソッド呼び出しを変更して、明示的な型情報を提供する場合:

MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");

これで、型Kは と推測されますIntWrapperが、1は に変換できないためIntWrapper、そのメソッドは破棄され、2 番目のメソッドが呼び出され、完全に正常に動作します。


率直に言って、ここで何が起こっているのか本当にわかりません。最初のケースでも、次の問題で機能するため、コンパイラがメソッド呼び出しコンテキストから型パラメーターを推測することを期待します。

public static <T> HashSet<T> create(int size) {  
    return new HashSet<T>(size);  
}
// Type inferred as `Integer`, from LHS.
HashSet<Integer> hi = create(10);  

しかし、この場合はそうではありません。したがって、これはバグである可能性があります。

*または、型が引数として渡されない場合に、コンパイラが型引数を推測する方法を正確に理解していない可能性があります。したがって、これについてさらに学習するために、JLS §15.12.2.7およびJLS §15.12.2.8を試してみました。これは、コンパイラが型引数を推測する方法に関するものですが、それは私の頭の中で完全に進んでいます。

したがって、今のところはそれを受け入れて、別の方法 (明示的な型引数を提供する) を使用する必要があります。


結局のところ、Compiler は何のトリックもしていませんでした。

@zhong.j.yu. のコメントで最終的に説明されているように、コンパイラは、15.12.2.7 セクションに従って推論に失敗した場合に、型推論にセクション 15.12.2.8 のみを適用します。ただし、ここでは、Integer渡された引数から型を推測できます。これは、明らかに型パラメーターがメソッドの形式パラメーターであるためです。

したがって、はい、コンパイラは型を as として正しく推論するIntegerため、あいまいさは有効です。そして今、この答えは完成したと思います。

于 2013-08-27T09:24:44.200 に答える
3

以下の 2 つの方法の違いは次のとおりです。方法 1:

   public MyContainer<IntWrapper> pack(int key, Object[] values) {
    return new MyContainer<>(new IntWrapper(""));
   }

方法 2:

public MyContainer<IntWrapper> pack(int key, Object ... values) {
    return new MyContainer<>(new IntWrapper(""));
}

方法2は同じくらい良いです

public MyContainer<IntWrapper> pack(Object ... values) {
    return new MyContainer<>(new IntWrapper(""));
 }

それがあなたが曖昧さを得る理由です..

編集 はい、コンパイルについては同じだと言いたいです。可変引数を使用する目的は、ユーザーが特定の型の引数の数がわからない場合にメソッドを定義できるようにすることです。

したがって、オブジェクトを可変引数として使用している場合は、送信するオブジェクトの数がわからないことをコンパイラーに伝えるだけでなく、「整数で不明な数のオブジェクトを渡しています」と言うだけです。コンパイラの場合、整数もオブジェクトです。

有効性を確認したい場合は、最初の引数として整数を渡してから、文字列の変数引数を渡してみてください。違いがわかります。

例:

public class Converter {
public static void a(int x, String... y) {
}

public static void a(String... y) {
}

public static void main(String[] args) {
    a(1, "2", "3");
}
}

また、配列と可変引数を同じ意味で使用しないでください。これらにはまったく異なる目的があります。

varargs を使用する場合、メソッドは配列ではなく、同じ型の異なるパラメーターを想定しており、インデックス付きの方法でアクセスできます。

于 2013-08-27T08:27:25.387 に答える
3

この場合

(1) m(K,   String[])
(2) m(int, String[])

m(1, new String[]{..});

m(1) は15.12.2.3 を満たします。フェーズ 2: メソッド呼び出しの変換によって適用可能な一致するアリティ メソッドを特定する

m(2) は15.12.2.2 を満たします。フェーズ 1: サブタイプによって適用可能なマッチング アリティ メソッドを特定する

コンパイラはフェーズ 1 で停止します。そのフェーズで適用可能な唯一の方法として m(2) が見つかったため、m(2) が選択されます。

var arg の場合

(3) m(K,   String...)
(4) m(int, String...)

m(1, str1, str2);

m(3) と m(4) の両方が15.12.2.4 を満たします。フェーズ 3: 適用可能な可変アリティ メソッドを特定します。どちらも他より具体的ではないため、あいまいです。

適用可能なメソッドを 4 つのグループにグループ化できます。

  1. サブタイプによって適用可能
  2. メソッド呼び出し変換で適用可能
  3. サブタイプによって適用可能な vararg
  4. メソッド呼び出しの変換によって適用される vararg

仕様は、グループ 3 と 4 をマージし、両方をフェーズ 3 で扱います。したがって、矛盾が生じます。

なぜ彼らはそれをしたのですか?彼らはそれにうんざりしているかもしれません。

別の批判は、プログラマーはそのように考えないので、これらすべてのフェーズがあってはならないということです。適用可能なすべてのメソッドを無差別に見つけてから、最も具体的なメソッドを選択する必要があります (ボックス化/ボックス化解除を回避するためのメカニズムを備えています)。

于 2013-08-30T15:08:09.167 に答える
0

まず第一に、これは最初の手がかりにすぎません...もっと編集するかもしれません。

コンパイラは常に、利用可能な最も具体的なメソッドを検索して選択します。読むのは少し不器用ですが、すべてJLS 15.12.2.5で指定されています。したがって、呼び出すことによって

converter.pack(1, "テスト", "テスト2" )

がまたは1に分解されるかどうかは、コンパイラでは判断できません。つまり、K はどの型にも適用できるので、int/Integer と同じレベルです。Kint

違いは、引数の数と型にあります。new String[]{"Test", "Test2"}これは配列であると考えてください。一方、"Test", "Test2"は String 型の 2 つの引数です!

コンバーター.パック(1); // あいまい、コンパイラ エラー

converter.pack(1, null); // メソッド 2 を呼び出し、コンパイラの警告

converter.pack(1, 新しい文字列[]{}); // メソッド 2 を呼び出し、コンパイラの警告

converter.pack(1, new Object());// あいまい、コンパイラ エラー

converter.pack(1, new Object[]{});// メソッド 2 を呼び出し、警告なし

于 2013-08-27T08:55:53.347 に答える