2010-02-23 18 views
21

Mi comprensión obviamente errónea de los genéricos de Java fue hasta ahora, que Type Erasure elimina toda la información de tipo, de modo que no queda nada en el tiempo de ejecución. Recientemente me encontré con un fragmento de código donde tuve que preguntarme a mí mismo: ¿Cómo funciona esto? Simplificada, se presenta como:¿Por qué no se borran todos los tipos de información en Java en tiempo de ejecución?

import java.lang.reflect.ParameterizedType; 
import java.lang.reflect.Type; 

public abstract class SuperClass<T> { 

    private final Type type; 

    protected SuperClass(){ 
     ParameterizedType parameterizedType = 
       (ParameterizedType) getClass().getGenericSuperclass(); 
     type = parameterizedType.getActualTypeArguments()[0]; 
    } 

    public void tellMyType(){ 
     System.out.println("Hi, my type parameter is " + type); 
    }  
} 

y

public class Example { 

    public static void main(String[] args) { 
     SuperClass sc = new SuperClass<Integer>(){}; 
     sc.tellMyType(); 
    } 
} 

La ejecución de los resultados de la clase principal en Hi, my type parameter is class java.lang.Integer.

Lo que podemos ver aquí es que la información de tipo de T también está disponible en tiempo de ejecución, lo que contradice mi comprensión inicial.

Así que mi pregunta es: ¿Por qué el compilador guarda esto? ¿Es esto necesario para algún comportamiento interno de JVM o hay alguna explicación razonable para este efecto?

Respuesta

16

De http://www.artima.com/weblogs/viewpost.jsp?thread=208860:

Resulta que mientras que la JVM no realizar un seguimiento de los argumentos de tipo reales de instancias de una clase genérica, hace un seguimiento de los argumentos de tipo reales de subclases de las clases genéricas . En otras palabras , mientras que un nuevo ArrayList<String>() es en realidad una nueva ArrayList() en tiempo de ejecución, si una clase extiende ArrayList<String>, entonces el JVM sabe que String es el argumento tipo real de List 's tipo parámetro.

En su caso, está haciendo una subclase anónima del tipo parametrizado, por lo que la información del tipo se conserva. Vea el artículo para una explicación detallada.

+0

Breve comentario: si su subclase simplemente pasa el tipo genérico (como en 'public class SuperList extiende ArrayList '), el resultado del código de ejemplo (sin clases anónimas involucradas) sería "Hola, mi parámetro de tipo es T" – sfussenegger

+0

Entonces, si hubieran omitido el '{}' en 'main' (que probablemente requeriría cambiar el constructor a public), entonces el hecho de que el parámetro type fuera' Integer' se habría perdido en el tiempo de ejecución. – MatrixFrog

14

parámetros de tipo están siendo borrados sólo de los tipos dinámicos (es decir, un tipo de objeto que está siendo creado):

Object o = new ArrayList<String>(); // String erased 

Está retenido en tipos estáticos (es decir, campo, argumento y tipos, throws cláusula, superclase de regreso y declaraciones superinterfaz):

class Test implements Superclass<String> { // String retained 
    // Accessible via Class.getGenericSuperclass() 

    private List<Integer> l; // Integer retained (via Field.getGenericType()) 

    public void test(List<Long> l) {} // Long retained (via Method.getGenericParameterTypes()) 

    // Character retained (via Method.getGenericReturnType()) 
    public List<Character> test() { return null; } 
} 

en su caso, se crea una subclase anónima de SuperClass<Integer>, por lo que el parámetro tipo se conserva en la declaración superclase.

3

Google Guice usa esto para crear TypeLiteral s para representar clases genéricas en tiempo de ejecución. Por ejemplo

TypeLiteral<List<String>> list = new TypeLiteral<List<String>>() {}; 

se puede utilizar, pero

Class<List<String>> list = List<String>.class; 

no se compilará.

La técnica se conoce como "token de tipo super" (ver Neal Gafter's article sobre el tema).

0

No pude hacer que ParameterizedType me funcionara (no pude cambiar el código de SuperClass). Sin embargo, el siguiente código simple funcionó bien:

try 
    { 
    SuperClass<Integer> castSc = (SuperClass<Integer>)sc; 
    // Do what you want to do if generic type is Integer... 
    } 
catch (ClassCastException ignored) 
    { 
    } 
Cuestiones relacionadas