以下のプログラムは、Java 7およびEclipse Mars RC2でJava 8:
import Java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
b(newList(type));
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Javac 1.8.0_45コンパイラを使用すると、次のコンパイルエラーが報告されます。
Test.Java:6: error: method b in class Test cannot be applied to given types;
b(newList(type));
^
required: List<T>
found: CAP#1
reason: inference variable L has incompatible bounds
equality constraints: CAP#2
upper bounds: List<CAP#3>,List<?>
where T,L are type-variables:
T extends Object declared in method <T>b(List<T>)
L extends List<?> declared in method <L>newList(Class<L>)
where CAP#1,CAP#2,CAP#3 are fresh type-variables:
CAP#1 extends List<?> from capture of ? extends List<?>
CAP#2 extends List<?> from capture of ? extends List<?>
CAP#3 extends Object from capture of ?
回避策は、変数をローカルに割り当てることです。
import Java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
// Workaround here
List<?> variable = newList(type);
b(variable);
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
型の推論は、Java 8( 例:JEP 101 "generalized target-type inference" )で大きく変化したことを知っています。新しい言語の「機能」?
[〜#〜] edit [〜#〜]:これもJI-9021550としてOracleに報告しましたが、念のため、 Java 8、Eclipseにも問題を報告しました:
バグレポート に感謝します。回答の例については、Holgerに感謝します。これらと他のいくつかはついに、11年前にEclipseコンパイラで行われた1つの小さな変更を疑問視させました。ポイントは、Eclipseがキャプチャアルゴリズムを違法に拡張して、ワイルドカードの境界に再帰的に適用することでした。
この違法な変更が、Eclipseの動作をjavacと完全に整合させた1つの例がありました。 Eclipseの世代の世代は、JLSで明確に見ることができるものよりも、この古い決定を信頼しています。今日、以前の逸脱には別の理由があったに違いないと考えています。
今日、この点でecjをJLSに合わせるために勇気を出しました。非常にクラックしにくいと思われる5つのバグは、基本的にそのように解決されました(さらに、補償としてあちこちで微調整します)。
Ergo:はい、Eclipseにはバグがありましたが、そのバグは4.7マイルストーン2で修正されました:)
以下は、ecjが今後報告する内容です。
The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)
互換性を検出するルールを見つけられないのは、キャプチャバウンド内のワイルドカードです。より正確には、推論中(正確には組み込み)に、次の制約(T#0は推論変数を表します)に遭遇します。
⟨T#0 = ?⟩
単純に、型変数をワイルドカードに解決することはできますが、おそらくワイルドカードは型と見なされないため、リダクションルールは上記をFALSEに還元するものとして定義し、推論が失敗するようにします。
免責事項-私は主題について十分に知りません、そして、以下はjavacの振る舞いを正当化しようとする私の非公式の推論です。
問題を減らすことができます
<X extends List<?>> void a(Class<X> type) throws Exception
{
X instance = type.newInstance();
b(instance); // error
}
<T> List<T> b(List<T> list) { ... }
T
を推測するには、制約があります
X <: List<?>
X <: List<T>
基本的に、これは解決できません。たとえば、X=List<?>
の場合、T
は存在しません。
Java7がこのケースをどのように推測するかわかりません。しかし、javac8(およびIntelliJ)は「合理的に」振る舞うと思います。
さて、どうしてこの回避策が機能するのでしょうか?
List<?> instance = type.newInstance();
b(instance); // ok!
ワイルドカードキャプチャにより機能します。これにより、instance
のタイプを「絞り込み」、より多くのタイプ情報が導入されます。
instance is List<?> => exist W, where instance is List<W> => T=W
残念ながら、これはinstance
がX
の場合は行われないため、処理する型情報が少なくなります。
おそらく、言語も「改善」されて、Xのワイルドカードキャプチャも可能になります。
instance is X, X is List<?> => exist W, where instance is List<W>
bayou.ioの回答 のおかげで、次の事実に問題を絞り込むことができます。
<X extends List<?>> void a(X instance) {
b(instance); // error
}
static final <T> List<T> b(List<T> list) {
return list;
}
エラーを生成します
<X extends List<?>> void a(X instance) {
List<?> instance2=instance;
b(instance2);
}
static final <T> List<T> b(List<T> list) {
return list;
}
問題なくコンパイルできます。 instance2=instance
の割り当ては、メソッド呼び出し引数に対しても発生するはずの拡大変換です。 this answer のパターンとの違いは、追加のサブタイプ関係です。
この特定のケースがJava Language Specificationに沿っているかどうかはわかりませんが、一部のテストでは、Eclipseがコードを受け入れているのは、一般的に、一般的な型は、次のように間違いなく正しくないため、エラーや警告なしにコードをコンパイルできます。
public static void main(String... arg) {
List<Integer> l1=Arrays.asList(0, 1, 2);
List<String> l2=Arrays.asList("0", "1", "2");
a(Arrays.asList(l1, l2));
}
static final void a(List<? extends List<?>> type) {
test(type);
}
static final <Y,L extends List<Y>> void test(List<L> type) {
L l1=type.get(0), l2=type.get(1);
l2.set(0, l1.get(0));
}