web-dev-qa-db-ja.com

ジェネリックの動作はJDK 8と9で異なります

次の簡単なクラス( 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バージョンによって異なります。

  • JDK <= 8で適切にコンパイルする(7と8でテスト済み)
  • JDK 9+を使用してコンパイルが失敗する(9、10、11 EAでテスト済み)

次のエラーが発生しました:

[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 +ではできませんか?

33
alostale

いくつかの調査の後、私はこれを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を必要とする新しいコードについては、総称値を完全に指定することです。

11
flakes

型推論の違いを示す別の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にはNList<? super String>に解決するバグがあるようです

Matcher<N> anyOf(Matcher<N> first, Matcher<? super N> second)

呼び出すとき

anyOf(Matcher<List<? super String>>, Matcher<List<? super String>>)

私はこの問題をOpenJDKに報告(この問題をここにリンク)し、場合によってはhamcrestプロジェクトに問題を報告することをお勧めします。

1
tkruse