what - return generic method java
Java Generics Hell (2)
Sospecho que esto se ha preguntado aquí (y contestado) antes, pero no sé cómo nombrar el problema. ¿Por qué puedo expresar los comodines sin problema solo cuando no estoy pasando la clase en sí?
Todo se reduce a este código. Todo funciona como se espera, excepto la llamada a genericsHell(ShapeSaver.class)
:
interface Shape { }
interface Circle extends Shape { }
interface ShapeProcessor<T extends Shape> { }
class CircleDrawer implements ShapeProcessor<Circle> { }
class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }
class Test {
void genericsHeaven(ShapeProcessor<? extends Shape> a) {}
void genericsHell(Class<? extends ShapeProcessor<? extends Shape>> a) {}
void test() {
genericsHeaven(new CircleDrawer());
genericsHeaven(new ShapeSaver<Circle>());
genericsHell(CircleDrawer.class);
genericsHell(ShapeSaver.class); // ERROR: The method genericsHell is not applicable for the arguments (Class<ShapeSaver>)
}
}
El tipo de ShapeSaver.class
es Class<ShapeSaver>
. Cuando se alimenta a genericsHell()
, el compilador debe verificar si la Class<ShapeSaver>
es un subtipo de Class<? extends ShapeProcessor<?>
Class<? extends ShapeProcessor<?>
, que se reduce a si ShapeSaver
es un subtipo de ShapeProcessor<?>
. La relación de subtipo no se mantiene, la llamada al método falla.
Lo mismo debería ser cierto para la solución de @Bohemian. Aquí, la comprobación de subtipo se produce en la comprobación enlazada de T
después de inferir T
Debería fallar también. Esto parece ser un error del compilador, que de alguna manera malinterpreta la regla de que Raw
se puede asignar a Raw<X>
como si Raw
sea un subtipo de Raw<X>
. vea también Enum.valueOf lanza una advertencia para un tipo desconocido de clase que extiende Enum?
Una solución simple a tu problema es declarar.
void genericsHell(Class<? extends ShapeProcessor> a)
de hecho, ShapeSaver
es un subtipo de ShapeProcessor
, y la llamada se compila.
Eso no es sólo una solución. Hay una buena razón para ello. Estrictamente hablando, para cualquier Class<X>
, X
debe ser un tipo sin formato. Por ejemplo, la Class<List>
está bien, la Class<List<String>>
no lo está. Porque realmente no hay ninguna clase que represente List<string>
; sólo hay una clase que representa a la List
.
Ignore la advertencia de popa de que no debe utilizar el tipo raw. Debemos usar tipos brutos a veces, dado cómo se diseña el sistema de tipo Java. Incluso las API principales de Java ( Object.getClass()
) utilizan tipos sin Object.getClass()
.
Probablemente pretendías hacer algo como esto.
genericsHell(ShapeSaver<Circle>.class);
Desafortunadamente, eso no está permitido. Java podría, pero no lo hizo, introducir el tipo literal junto con los genéricos. Eso creó muchos problemas para muchas bibliotecas. java.lang.reflect.Type
es un desorden e inutilizable. Cada biblioteca tiene que introducir su propia representación del sistema de tipos para resolver el problema.
Puede pedir uno prestado, por ejemplo, de Guice, y podrá
genericsHell( new TypeLiteral< ShapeSaver<Circle> >(){} )
------------------
(aprende a saltar los dados alrededor de ShaveSaver<Circle>
al leer el código)
En el cuerpo del método de genericsHell()
, tendrá información de tipo completo, no solo la clase.
Escribir el método genericsHell
permite compilar:
static <T extends ShapeProcessor<?>> void genericsHell(Class<T> a) {}
EDITADO: Esto permite que el compilador especifique desde el contexto, o codificando un tipo explícito, que el ShapeProcessor
no es literalmente ningún ShapeProcessor
, sino el mismo tipo que el pasado como parámetro. Si la llamada se escribiera explícitamente, (lo que hace el compilador bajo las cubiertas) el código se vería así:
MyClass.<ShapeSaver>genericsHell(ShapeSaver.class);
Que curiosamente, da una advertencia de tipo, pero aún así se compila. Sin embargo, no se requiere el tipo explícito porque hay suficiente información de tipo disponible en el parámetro para infer el tipo genérico.
A su pregunta le faltan algunas declaraciones, así que las agregué para crear un Ejemplo Corto Autocontenido Corto , es decir, este código se compila como está
static interface Shape { }
static interface Circle extends Shape { }
static interface ShapeProcessor<T extends Shape> { }
static class CircleDrawer implements ShapeProcessor<Circle> { }
static class ShapeSaver<T extends Shape> implements ShapeProcessor<T> { }
static void genericsHeaven(ShapeProcessor<? extends Shape> a) { }
// The change was made to this method signature:
static <T extends ShapeProcessor<?>> void genericsHell(Class<T> a) { }
static void test() {
genericsHeaven(new CircleDrawer());
genericsHeaven(new ShapeSaver<Circle>());
genericsHell(CircleDrawer.class);
genericsHell(ShapeSaver.class);
}