Le comportement des génériques diffère dans les JDK 8 et 9

La classe simple suivante ( repo pour le reproduire):

import static org.hamcrest.*; import static org.junit.Assert.assertThat; import java.util.*; import org.junit.Test; public class TestGenerics { @Test public void thisShouldComstack() { List myList = Arrays.asList("a", "b", "c"); assertThat("List doesn't contain unexpected elements", myList, not(anyOf(hasItem("d"), hasItem("e"), hasItem("f")))); } } 

Le comportement dépend de la version du JDK:

  • Comstack correctement dans JDK <= 8 (testé avec 7 et 8)
  • La compilation échoue avec JDK 9+ (testé avec EA 9, 10 et 11)

Avec l’erreur suivante:

 [ERROR] /tmp/jdk-issue-generics/src/test/java/org/alostale/issues/generics/TestGenerics.java:[17,17] no suitable method found for assertThat(java.lang.Ssortingng,java.util.List,org.hamcrest.Matcher<java.lang.Iterable>) method org.junit.Assert.assertThat(java.lang.Ssortingng,T,org.hamcrest.Matcher) is not applicable (inference variable T has incompatible bounds upper bounds: java.lang.Ssortingng,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.Ssortingng,capture#4 of ? super T?) method org.junit.Assert.assertThat(T,org.hamcrest.Matcher) is not applicable (cannot infer type-variable(s) T (actual and formal argument lists differ in length)) 

S’agit-il d’un changement attendu dans JDK 9 ou d’un bogue?

Je pourrais extraire des correspondances avec des variables typées de cette manière, et cela fonctionnerait:

  Matcher<Iterable> m1 = hasItem("d"); Matcher<Iterable> m2 = hasItem("e"); Matcher<Iterable> m3 = hasItem("f"); assertThat(myList, not(anyOf(m1, m2, m3))); 

Mais la question est toujours de savoir si javac <= 8 permet de déduire des types, mais pas dans 9+.

Après quelques recherches, je crois que nous pouvons exclure cela comme un problème de Junit ou de Hamcrest. En effet, cela semble être un bug JDK. Le code suivant ne sera pas compilé dans JDK> 8:

 AnyOf> 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.Ssortingng lower bounds: java.lang.Object,java.lang.Ssortingng 

Turing ceci dans un MCVE qui n’utilise aucune bibliothèque:

 class Test { class A { } class B { } class C { } class D { }  A> foo() { return null; }  C bar(A a1, A a2) { return null; } C> c = bar(foo(), foo()); } 

Un effet similaire peut être obtenu en utilisant une seule variable dans bar qui résulte en une contrainte d’égalité des limites supérieures par opposition à une contrainte inférieure:

 class Test { class A { } class B { } class C { } class D { }  A> foo() { return null; }  C bar(A a) { return null; } C> c = bar(foo()); } 
 Error:(21, 28) java: incompatible types: inference variable U has incompatible bounds equality constraints: com.Test.B upper bounds: com.Test.B,java.lang.Object 

Cela ressemble à quand le JDK tente de rationaliser ? super U ? super U il ne parvient pas à trouver la classe générique appropriée à utiliser. Encore plus intéressant, si vous spécifiez complètement le type de foo , le compilateur réussira. Cela est vrai à la fois pour MCVE et le message original :

 // This causes comstack to succeed even though an IDE will call it redundant C> c = bar(this.foo(), this.foo()); 

Et comme dans le cas que vous avez présenté, la division de l’exécution en plusieurs lignes donnera les bons résultats:

 A> a1 = foo(); A> a2 = foo(); C> c = bar(a1, a2); 

Étant donné qu’il existe de nombreuses façons d’écrire ce code qui devrait être fonctionnellement équivalent, et étant donné que seules certaines d’entre elles sont compilées, ma conclusion est que ce n’est pas le comportement souhaité du JDK. Il y a un bogue quelque part dans l’évaluation des caractères génériques qui ont une super borne.

Ma recommandation serait de comstackr le code existant contre JDK 8, et pour le code plus récent nécessitant JDK> 8, de spécifier complètement la valeur générique.

J’ai créé un MCVE différent montrant une différence dans l’inférence de type:

 import java.util.Arrays; import java.util.List; public class Example { public class Matcher { private T t; public Matcher(T t) { this.t = t; } } public  Matcher anyOf(Matcher first, Matcher second) { return first; } public  Matcher> hasItem1(T item) { return new Matcher<>(Arrays.asList(item)); } public  Matcher> hasItem2(T item) { return new Matcher<>(Arrays.asList(item)); } public void thisShouldCompile() { Matcher x = (Matcher>) anyOf(hasItem1("d"), hasItem2("e")); } } 

JDK8 comstack passes, JDK10 donne:

 Example.java:27: error: incompatible types: Example.Matcher> cannot be converted to Example.Matcher> Matcher x = (Matcher>) anyOf(hasItem1("d"), hasItem2("e")); 

Il semble donc que JDK10 ait un bug qui résout N en List List dans

 Matcher anyOf(Matcher first, Matcher second) 

en appelant

 anyOf(Matcher>, Matcher>) 

Je recommanderais de signaler ce problème à OpenJDK (en reliant le problème ici) et éventuellement de signaler le problème au projet hamcrest.