java eclipse generics java-8 javac

El comodín de límite inferior causa problemas en javac, pero no en Eclipse



generics java-8 (2)

Este fragmento de código se compila en Eclipse pero no en javac:

import java.util.function.Consumer; public class Test { public static final void m1(Consumer<?> c) { m2(c); } private static final <T> void m2(Consumer<? super T> c) { } }

salida javac:

C:/Users/lukas/workspace>javac -version javac 1.8.0_92 C:/Users/lukas/workspace>javac Test.java Test.java:5: error: method m2 in class Test cannot be applied to given types; m2(c); ^ required: Consumer<? super T> found: Consumer<CAP#1> reason: cannot infer type-variable(s) T (argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>) where T is a type-variable: T extends Object declared in method <T>m2(Consumer<? super T>) where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ? 1 error ----------------------------------------------------------------

¿Qué compilador está mal y por qué? ( Informe de error de Eclipse y partes de discusión aquí )


Este código es legal y JLS 8. javac versión 8 y anteriores tenían varios errores en la forma en que manejan comodines y capturas. A partir de la versión 9 (acceso temprano, probé la versión ea-113 y la más reciente), también javac acepta este código.

Para comprender cómo un compilador analiza esto de acuerdo con JLS, es esencial distinguir qué son las capturas comodín, las variables de tipo, las variables de inferencia y demás.

El tipo de c es Consumer<capture#1-of ?> (Javac escribiría Consumer<CAP#1> ). Este tipo es desconocido, pero arreglado.

El parámetro de m2 tiene tipo Consumer<? super T> Consumer<? super T> , donde T es una variable de tipo a ser instanciada por inferencia de tipo.

Durante la inferencia de tipo, se utiliza una variable de inferencia , indicada por ecj como T#0 , para representar T

La inferencia de tipos consiste en determinar si T#0 se puede crear una instancia de cualquier tipo sin violar ninguna restricción de tipo dada. En este caso particular comenzamos con este contraint:

→c → Consumidor <? super t # 0>⟩

Que se reduce gradualmente (aplicando JLS 18.2 ):

⟨Consumidor <captura # 1-de?> → Consumidor <? super t # 0>⟩

⟨Captura # 1-de? <=? super t # 0⟩

⟨T # 0 <: captura # 1-de?⟩

T # 0 <: captura # 1-de?

La última línea es un "tipo enlazado" y se realiza la reducción. Dado que no hay más restricciones involucradas, la resolución crea una instancia trivial de T#0 al tipo capture#1-of ? .

Mediante estos pasos, la inferencia de tipos ha demostrado que m2 es aplicable para esta invocación en particular. qed

Intuitivamente, la solución mostrada nos dice: cualquiera que sea el tipo que represente la captura, si T está configurado para representar exactamente el mismo tipo, no se infringen las restricciones de tipo. Esto es posible, porque la captura es fija antes de iniciar la inferencia de tipos.


Tenga en cuenta que lo siguiente se puede compilar sin problemas:

public class Test { public static final void m1(Consumer<?> c) { m2(c); } private static final <T> void m2(Consumer<T> c) { } }

A pesar de que no sabemos el tipo real de consumidor, sabemos que será asignable a Consumer<T> , aunque no sabemos qué es T (al no saber qué es T , es la norma en el código genérico) .

Pero si la asignación al Consumer<T> es válida, la asignación al Consumer<? super T> Consumer<? super T> lo sería. Incluso podemos mostrar esto prácticamente con un paso intermedio:

public class Test { public static final void m1(Consumer<?> c) { m2(c); } private static final <T> void m2(Consumer<T> c) { m3(c); } private static final <T> void m3(Consumer<? super T> c) { } }

Ningún compilador se opone a eso.

También se aceptará cuando reemplace el comodín con un tipo nombrado, por ejemplo,

public class Test { public static final void m1(Consumer<?> c) { m2(c); } private static final <E,T extends E> void m2(Consumer<E> c) { } }

Aquí, E es un super tipo de T , como ? super T ? super T es.

Traté de encontrar el informe de errores para javac más cercano a este escenario, pero cuando se trata de tipos de javac y comodines, hay tantos de ellos que finalmente me di por vencido. Descargo de responsabilidad: esto no implica que haya tantos errores, solo que hay tantos escenarios relacionados informados, que pueden ser síntomas diferentes del mismo error.

Lo único que importa es que ya está solucionado en Java 9.