次の簡単なクラス( repo で再現):
import static org.hamcrest.*;
import static org.junit.Assert.assertThat;
import Java.util.*;
import org.junit.Test;
public class TestGenerics {
@Test
public void thisShouldCompile() {
List<String> myList = Arrays.asList("a", "b", "c");
assertThat("List doesn't contain unexpected elements", myList, not(anyOf(hasItem("d"), hasItem("e"), hasItem("f"))));
}
}
動作はJDKバージョンによって異なります。
次のエラーが発生しました:
[ERROR] /tmp/jdk-issue-generics/src/test/Java/org/alostale/issues/generics/TestGenerics.Java:[17,17] no suitable method found for assertThat(Java.lang.String,Java.util.List<Java.lang.String>,org.hamcrest.Matcher<Java.lang.Iterable<? super Java.lang.Object>>)
method org.junit.Assert.<T>assertThat(Java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
(inference variable T has incompatible bounds
upper bounds: Java.lang.String,Java.lang.Object
lower bounds: capture#1 of ? super T?,capture#2 of ? super Java.lang.Object,capture#3 of ? super Java.lang.Object,Java.lang.Object,Java.lang.String,capture#4 of ? super T?)
method org.junit.Assert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
これはJDK 9で予想される変更ですか、それともバグですか?
このようにして、型指定された変数へのマッチャーを抽出できます。
Matcher<Iterable<? super String>> m1 = hasItem("d");
Matcher<Iterable<? super String>> m2 = hasItem("e");
Matcher<Iterable<? super String>> m3 = hasItem("f");
assertThat(myList, not(anyOf(m1, m2, m3)));
しかし、それでも問題は次のとおりです。それは正しいですかjavac
<= 8は型を推論できますが、9 +ではできませんか?
いくつかの調査の後、私はこれをJUnitまたはハムクレストの問題として除外できると信じています。実際、これはJDKのバグのようです。次のコードは、JDK> 8ではコンパイルできません。
AnyOf<Iterable<? super String>> matcher = CoreMatchers.anyOf(
CoreMatchers.hasItem("d"), CoreMatchers.hasItem("e"), CoreMatchers.hasItem("f"));
Error:(23, 63) Java: incompatible types: inference variable T has incompatible bounds equality constraints: Java.lang.String lower bounds: Java.lang.Object,Java.lang.String
ライブラリを使用しないMCVEにこれを調整します。
class Test {
class A<S> { } class B<S> { } class C<S> { } class D { }
<T> A<B<? super T>> foo() { return null; }
<U> C<U> bar(A<U> a1, A<? super U> a2) { return null; }
C<B<? super D>> c = bar(foo(), foo());
}
bar
の単一の変数を使用して同様の効果を達成できます。これにより、下限とは対照的に上限の等式制約が発生します。
class Test {
class A<S> { } class B<S> { } class C<S> { } class D { }
<T> A<B<? super T>> foo() { return null; }
<U> C<U> bar(A<? super U> a) { return null; }
C<B<? super D>> c = bar(foo());
}
Error:(21, 28) Java: incompatible types: inference variable U has incompatible bounds equality constraints: com.Test.B<? super com.Test.D> upper bounds: com.Test.B<? super capture#1 of ? super com.Test.D>,Java.lang.Object
JDKが? super U
を合理化しようとしているときに、使用する適切なワイルドカードクラスを見つけることができないようです。さらに興味深いことに、foo
のタイプを完全に指定すると、コンパイラーは実際に成功します。 これはMCVEと元の投稿の両方に当てはまります:
// This causes compile to succeed even though an IDE will call it redundant
C<B<? super D>> c = bar(this.<D>foo(), this.<D>foo());
そして、あなたが提示した場合と同様に、実行を複数行に分割すると、正しい結果が生成されます。
A<B<? super D>> a1 = foo();
A<B<? super D>> a2 = foo();
C<B<? super D>> c = bar(a1, a2);
機能的に同等であるはずのこのコードを記述する方法は複数あり、それらの一部のみがコンパイルされることを考えると、これはJDKの意図された動作ではないという結論に達しました。 super
がバインドされたワイルドカードの評価のどこかにバグがあります。
私の推奨は、JDK 8に対して既存のコードをコンパイルし、JDK> 8を必要とする新しいコードについては、総称値を完全に指定することです。
型推論の違いを示す別のMCVEを作成しました。
import Java.util.Arrays;
import Java.util.List;
public class Example {
public class Matcher<T> {
private T t;
public Matcher(T t) {
this.t = t;
}
}
public <N> Matcher<N> anyOf(Matcher<N> first, Matcher<? super N> second) {
return first;
}
public <T> Matcher<List<? super T>> hasItem1(T item) {
return new Matcher<>(Arrays.asList(item));
}
public <T> Matcher<List<? super T>> hasItem2(T item) {
return new Matcher<>(Arrays.asList(item));
}
public void thisShouldCompile() {
Matcher x = (Matcher<List<? super String>>) anyOf(hasItem1("d"), hasItem2("e"));
}
}
JDK8コンパイルパス、JDK10は以下を提供します。
Example.Java:27: error: incompatible types: Example.Matcher<List<? super Object>> cannot be converted to Example.Matcher<List<? super String>>
Matcher x = (Matcher<List<? super String>>) anyOf(hasItem1("d"), hasItem2("e"));
JDK10にはN
をList<? super String>
に解決するバグがあるようです
Matcher<N> anyOf(Matcher<N> first, Matcher<? super N> second)
呼び出すとき
anyOf(Matcher<List<? super String>>, Matcher<List<? super String>>)
私はこの問題をOpenJDKに報告(この問題をここにリンク)し、場合によってはhamcrestプロジェクトに問題を報告することをお勧めします。