programacion - que son generics java
¿Por qué este uso de Generics no lanza una excepción de tiempo de ejecución o de compilación? (3)
Tengo un método en una clase que tiene un tipo de retorno especificado por el uso de un genérico.
public class SomeMain {
public static void main(String[] args) {
Foo<Integer> foo = new Foo<Integer>();
System.out.println(foo.getFoo()); // Works, prints out "Foo"
}
public static class Foo<E> {
public E getFoo() {
return (E) "Foo";
}
}
}
Con el tipo de retorno genérico, asumí que el retorno en el ejemplo anterior se evaluaría para:
return (Integer) "Foo"; // Inconvertible types !!!
En su lugar, una String
se devuelve e imprime correctamente.
Recibo un error de compilación si cambio la llamada a ser:
String fooString = foo.getFoo(); // Compile error, incompatible types found
System.out.println(fooString);
¿Qué es lo que me falta para ayudarme a comprender lo que está pasando aquí y por qué la versión original no produjo un error de compilación?
Esto se debe a que la resolución de sobrecarga resolvió su llamada a println(Object)
, ya que no hay println(Integer)
.
Tenga en cuenta que los genéricos de Java se borran en tiempo de ejecución. Y los lanzamientos como (E) "Foo"
se eliminan y se mueven para llamar al sitio. A veces esto no es necesario, por lo que las cosas se lanzan al tipo correcto solo cuando es necesario.
En otras palabras, no se realizan getFoo
dentro de getFoo
. La especificación de idioma soporta esto:
Sección 5.5.2 Fundiciones revisadas y no comprobadas
El elenco es un elenco completamente sin marcar.
No se realiza ninguna acción en tiempo de ejecución para tal lanzamiento.
Después de borrar, getFoo
devuelve Object
. Y eso se pasa a println(Object)
, que está perfectamente bien.
Si llamo a este método y paso foo.getFoo
, obtendré un error:
static void f(Integer i) {
System.out.println(i);
}
// ...
f(foo.getFoo()); // ClassCastException
porque esta vez necesita ser fundido.
Me gustaría agregar que durante el proceso de borrado de tipo , el compilador de Java reemplaza el parámetro de tipo ilimitado E
con Object
, por lo tanto, la clase Foo
se compila en:
public static class Foo {
public Object getFoo() {
return "Foo";
}
}
Es por eso que el siguiente código es válido (no se necesita cast):
Object obj = foo.getFoo();
System.out.println(obj);
Al mismo tiempo, el siguiente fragmento de código produce un error en tiempo de compilación, como se esperaba:
Foo<Integer> foo = new Foo<Integer>();
String fooString = foo.getFoo(); // you''re trying to trick the compiler (unsuccessfully)
^
incompatible types: Integer can not be converted to String
Y esa es la principal responsabilidad de los genéricos : los controles de tiempo de compilación.
Sin embargo, hay otra cara de la historia: los moldes en tiempo de ejecución. Por ejemplo, si escribes:
Integer value = foo.getFoo();
obtienes una ClassCastException
lanzada en tiempo de ejecución (el compilador de Java inserta una instrucción de checkcast
que examina si el resultado de foo.getFoo()
se puede convertir a Integer
).
System.out.println
no tiene una sobrecarga que tome Integer
. Así que esta declaración:
System.out.println(foo.getFoo());
Está llamando System.out.println(Object);
.
Para verificar que de lo contrario fallaría, intente:
Foo<Integer> foo = new Foo<Integer>();
Integer fooInt = foo.getFoo(); //class cast exception
Lo siguiente fallará de la misma manera:
public static void main(String[] args) throws Exception {
Foo<Integer> foo = new Foo<Integer>();
print(foo.getFoo()); //Would also fail with a class cast exception
}
static void print(Integer in) {
System.out.println(in);
}
Y esta es una compilación fallida por razones obvias:
String fooString = foo.getFoo(); //can''t work
foo
es Foo<Integer>
, y foo.getFoo()
devuelve un Integer
y el compilador puede recogerlo.