new features java generics java-8 java-9 java-10

java 11 new features



El comportamiento genérico es diferente en JDK 8 y 9 (2)

Creé un MCVE diferente que muestra una diferencia en la inferencia de tipo:

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

Pases de compilación JDK8, JDK10 da:

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

Entonces parece que JDK10 tiene un error al resolver N a List<? super String> List<? super String> en

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

cuando llamas

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

Recomendaría informar este problema a OpenJDK (relacionando el problema aquí) y posiblemente informar el problema al proyecto de Hamcrest.

La siguiente clase simple ( repo para reproducirlo):

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

El comportamiento depende de la versión JDK:

  • Se compila correctamente en JDK <= 8 (probado con 7 y 8)
  • La compilación falla utilizando JDK 9+ (probado con 9, 10 y 11 EA)

Con el siguiente error:

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

¿Este es un cambio esperado en JDK 9 o es un error?

Podría extraer los matchers de las variables tipadas de esta manera, y funcionaría:

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

Pero aún la pregunta es: ¿es correcto que javac <= 8 pueda inferir tipos, pero no en 9+?


Después de algunas investigaciones, creo que podemos descartar esto como un problema de Junit o Hamcrest. De hecho, esto parece ser un error JDK. El siguiente código no se compilará en 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 esto en un MCVE que no usa bibliotecas:

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

Se puede lograr un efecto similar usando una sola variable en la bar que da como resultado una restricción de igualdad de límites superiores en oposición a una menor:

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

Parece cuando el JDK intenta racionalizar ? super U ? super U no puede encontrar la clase de comodín adecuada para usar. Aún más interesante, si especifica completamente el tipo para foo , entonces el compilador realmente tendrá éxito. Esto es válido tanto para MCVE como para la publicación original :

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

Y al igual que en el caso que usted presentó, dividir la ejecución en múltiples líneas producirá los resultados correctos:

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

Debido a que hay varias formas de escribir este código que debería ser funcionalmente equivalente, y dado que solo algunos de ellos compilan, mi conclusión es que este no es el comportamiento previsto del JDK. Hay un error en algún lugar dentro de la evaluación de los comodines que tienen un super límite.

Mi recomendación sería compilar código existente contra JDK 8, y para código más nuevo que requiera JDK> 8, para especificar completamente el valor genérico.