15

私は次のクラスを持っています:

import java.util.HashSet;
import java.util.List;

public class OverloadTest<T> extends  HashSet<List<T>> {
  private static final long serialVersionUID = 1L;

  public OverloadTest(OverloadTest<? extends T> other) {}

  public OverloadTest(HashSet<? extends T> source) {}

  private OverloadTest<Object> source;

  public void notAmbigious() {
    OverloadTest<Object> o1 = new OverloadTest<Object>(source);
  }

  public void ambigious() {
    OverloadTest<Object> o2 = new OverloadTest<>(source);
  }
}

これは、JDK 7 の javac と eclipse (コンプライアンスを 1.7 または 1.8 に設定) で正常にコンパイルされます。ただし、JDK 8 の javac でコンパイルしようとすると、次のエラーが発生します。

[ERROR] src/main/java/OverloadTest.java:[18,35] reference to OverloadTest is ambiguous
[ERROR] both constructor <T>OverloadTest(OverloadTest<? extends T>) in OverloadTest and constructor <T>OverloadTest(java.util.HashSet<? extends T>) in OverloadTest match

このエラーは、メソッド内のコンストラクター呼び出しではambigous()なく、メソッド内のコンストラクター呼び出しにのみ適用されることに注意してくださいnotAmbiguous()。唯一の違いはambiguous()、ダイヤモンド演算子に依存していることです。

私の質問は次のとおりです。JDK 8 の javac はあいまいな解決策に適切にフラグを立てていますか、それとも JDK 7 の javac はあいまいさをキャッチできませんでしたか? 回答に応じて、JDK バグまたは ecj バグを報告する必要があります。

4

2 に答える 2

3

呼び出しでは、コンストラクターが T を明示的に設定して呼び出された場合、あいまいさはありません。

OverloadTest<Object> o1 = new OverloadTest<Object>(source);

T はコンストラクター呼び出し時に定義されているため、Object は ? を渡します。コンパイル時にオブジェクトチェックを拡張しても問題ありません。T が明示的に Object に設定されている場合、2 つのコンストラクターの選択肢は次のようになります。

public OverloadTest(OverloadTest<Object> other) {}
public OverloadTest(HashSet<Object> source) {}

この場合、コンパイラが最初のものを選択するのは非常に簡単です。他の例 (ひし形演算子を使用) では、T が明示的に設定されていないため、コンパイラは最初に実際のパラメーターの型をチェックして T を決定しようとしますが、最初のオプションでは必要ありませんでした。

2 番目のコンストラクターが適切に変更され、私が想像している操作が適切に反映された場合 (OverloadTest は T のリストの HashSet であるため、T のリストの HashSet を渡すことが可能になるはずです)、次のようになります。

public OverloadTest(HashSet<List<? extends T>> source) {}

...すると、あいまいさが解決されます。しかし、現状では、あいまいな呼び出しを解決するようコンパイラーに要求すると、競合が発生します。

コンパイラはひし形演算子を見て、渡されたものとさまざまなコンストラクターが期待するものに基づいて T を解決しようとします。しかし、HashSet コンストラクターの記述方法により、どのクラスが渡されても、両方のコンストラクターが有効なままであることが保証されます。これは、消去後、T が常に Object に置き換えられるためです。また、T が Object の場合、OverloadTest は HashSet の有効なインスタンスであるため、HashSet コンストラクターと OverloadTest コンストラクターは同様の消去を行います。また、1 つのコンストラクターが他のコンストラクターをオーバーライドしないため (OverloadTest<T> は HashSet<T> を拡張しないため)、実際には一方が他方よりも具体的であるとは言えません。選択を行い、代わりにコンパイル エラーをスローします。

これは、T を境界として使用することにより、コンパイラに型チェックを強制するためにのみ発生します。<? の代わりに単に <?> にした場合 extends T> は問題なくコンパイルできます。Java 8 コンパイラーは、型と消去に関して Java 7 よりも厳密です。これは、Java 8 の多くの新機能 (インターフェース防御メソッドなど) で、ジェネリックについてもう少し詳しく説明する必要があったためです。Java 7 は、これらのことを正しく報告していませんでした。

于 2014-10-09T04:02:30.933 に答える