34 votes

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

Le simple suivant la classe (pensions de 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 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"))));
  }
}

Le comportement dépend de la version du JDK:

  • Compile correctement dans le JDK<=8 (testé avec 7 et 8)
  • Compilation échoue à l'aide du JDK 9+ (testé avec 9, 10 et 11 EA)

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.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))

C'est une évolution prévue dans le JDK 9 ou c'est un bug?

J'ai pu extraire de rapprochement pour les variables de type de cette façon, et il devrait fonctionner:

    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)));

Mais encore, la question est: est-il exact javac <=8 est en mesure d'en déduire les types, mais pas dans 9+?

12voto

Calpratt Points 841

Après quelques recherches, je crois que nous pouvons règle comme Junit ou hamcrest question. En effet, ce qui semble être un JDK bug. Le code suivant ne compile pas dans le 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

Turing dans un MCVE qui n'utilise pas de bibliothèques:

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());
}

Un effet similaire peut être obtenu à l'aide d'une seule variable en bar qui résultats dans les limites supérieures de l'égalité de contrainte, par opposition à un plus bas:

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

Ça ressemble quand le JDK est d'essayer de rationaliser ? super U il ne parvient pas à trouver le bon générique de la classe à utiliser. Encore plus intéressant, si vous de bien spécifier le type foo, alors le compilateur va réussir réellement. Cela est vrai pour les deux MCVE et le original post:

// 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());

Et tout comme dans le cas présenté, la Rupture jusqu'à l'exécution en plusieurs lignes de produire des résultats corrects:

A<B<? super D>> a1 = foo();
A<B<? super D>> a2 = foo();
C<B<? super D>> c = bar(a1, a2);

Parce qu'il y a de multiples façons d'écrire ce code qui doit être fonctionnellement équivalent, et étant donné que seuls certains d'entre eux compiler, ma conclusion est que ce n'est pas le comportement souhaité de la JDK. Il y a un bug quelque part dans l'évaluation des jokers qui ont un super lié.

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

1voto

tkruse Points 3903

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

JDK8 compilez les passes, JDK10 donne :

Donc, il semble JDK10 a un bug de résolution à en

lors de l'appel

Je recommanderais de signaler ce problème à OpenJDK (liant la question ici), et peut-être signaler le problème au projet hamcrest.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X