son - polimorfismo java
¿Por qué es imposible implementar una interfaz genérica varias veces con diferentes tipos de parámetros? (2)
@Bozho: Gracias por tu profunda pregunta y tu propia respuesta. Incluso yo estaba en esta duda en algún momento.
Además, para agregar más a su respuesta, no estoy de acuerdo con usted en este punto de su pregunta:
msgstr "algunos tipos se retienen en el tiempo de ejecución" .
La respuesta es NO debido al borrado de tipo (que ya lo ha señalado). Nada queda retenido.
Ahora, para responder a esta duda clave, " ¿cómo sabe el tipo exacto después del borrado? ", Compruebe la definición de ParameterizedType .
Cuando se crea un tipo parametrizado p, se resuelve la declaración de tipo genérico que crea p.
Puede ser que esta resolución de TypeVariable exacta se obtenga a través de lo que especificó como métodos de puente
Tengo una interfaz:
public static interface Consumer<T> {
void consume(T t);
}
Y quiero poder tener:
public static class Foo implements Consumer<String>, Consumer<Integer> {
public void consume(String t) {..}
public void consume(Integer i) {..}
}
No funciona: el compilador no le permite implementar la misma interfaz dos veces.
La pregunta es: ¿por qué?
La gente ha hecho preguntas similares aquí, pero la respuesta siempre fue "borrado de tipo", es decir, no puede hacerlo porque los tipos se borran en tiempo de ejecución.
Y no lo son, algunos tipos se conservan en tiempo de ejecución. Y se mantienen en este caso particular:
public static void main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0];
System.out.println(type.getActualTypeArguments()[0]);
}
Esto imprime la class java.lang.String
(si solo mantengo Consumer<String>
para compilar)
Por lo tanto, el borrado, en su explicación más simple, no es la razón, o al menos necesita elaboración, el tipo está ahí y, además, no le importa la resolución de tipo, porque ya tiene dos métodos con firma distinta. O al menos eso parece.
La respuesta sigue siendo "borrado de tipo", pero no es tan simple. Las palabras clave son: tipos en bruto.
Imagina lo siguiente:
Consumer c = new Foo();
c.consume(1);
¿Qué haría eso? Parece que consume(String s)
no es realmente consume(String s)
- todavía es consume(Object o)
, a pesar de que está definido para tomar String
.
Por lo tanto, el código anterior es ambiguo: el tiempo de ejecución no puede saber cuál de los dos métodos de consume(..)
invocar.
Un ejemplo divertido de seguimiento es eliminar Consumer<Integer>
, pero mantener el método de consume(Integer i)
. A continuación, invocar el c.consume(1)
en un Consumer
bruto. emite ClassCastException
: no se puede convertir de Integer a String. Lo curioso de esta excepción es que sucede en la línea 1 .
La razón es el uso de métodos de puente . El compilador genera el método bridge:
public void consume(Object o) {
consume((String) o);
}
El bytecode generado es:
public void consume(java.lang.String);
public void consume(java.lang.Integer);
public void consume(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #39 // class java/lang/String
5: invokevirtual #41 // Method consume:(Ljava/lang/String;)V
Entonces, incluso si los métodos que ha definido conservan sus firmas, cada uno de ellos tiene un método de puente correspondiente que realmente se invoca cuando se trabaja con la clase (independientemente de si está en bruto o parametrizado).