2008-10-08 30 views
56

Estoy creando una clase genérica y en uno de los métodos necesito conocer la Clase del tipo genérico actualmente en uso. La razón es que uno de los métodos que llamo espera esto como un argumento.¿Cómo se determina la clase de un tipo genérico?

Ejemplo:

public class MyGenericClass<T> { 
    public void doSomething() { 
    // Snip... 
    // Call to a 3rd party lib 
    T bean = (T)someObject.create(T.class); 
    // Snip... 
    } 
} 

Es evidente que el ejemplo anterior no funciona y da como resultado el siguiente error: clase Ilegal literal para el parámetro de tipo T.

Mi pregunta es: ¿Alguien sabe un buen alternativa o solución para esto?

Respuesta

45

Sigue siendo el mismo problema: la información genérica se borra durante el tiempo de ejecución, no se puede recuperar. Una solución consiste en pasar la clase T en el parámetro de un método estático:

public class MyGenericClass<T> { 

    private final Class<T> clazz; 

    public static <U> MyGenericClass<U> createMyGeneric(Class<U> clazz) { 
     return new MyGenericClass<U>(clazz); 
    } 

    protected MyGenericClass(Class<T> clazz) { 
     this.clazz = clazz; 
    } 

    public void doSomething() { 
     T instance = clazz.newInstance(); 
    } 
} 

Es feo, pero funciona.

+0

Seguro que es feo, pero el truco del constructor protegido es suficiente para mí. Estoy usando la clase genérica como una clase abstracta que sirve como base de 5 a 10 clases concretas. ¡Gracias! –

+0

¿Es posible usar esto para determinar qué tipo genérico es un objeto? por ejemplo, 'if (obj.getClazz() es una Cadena) doThis(); if (obj.getClazz() es un Entero) doThat(); ' – dwjohnston

+1

^Solución: ' if (obj.getClazz(). Equals (String.class)) ... ' – dwjohnston

21

me acaba de señalar a esta solución:

import java.lang.reflect.ParameterizedType; 

public abstract class A<B> { 
    public Class<B> g() throws Exception { 
     ParameterizedType superclass = 
      (ParameterizedType) getClass().getGenericSuperclass(); 

     return (Class<B>) superclass.getActualTypeArguments()[0]; 
    } 
} 

Esto funciona si A se da un tipo concreto por una subclase:

new A<String>() {}.g() // this will work 

class B extends A<String> {} 
new B().g() // this will work 

class C<T> extends A<T> {} 
new C<String>().g() // this will NOT work 
+0

Buena solución. No confirmó que funcione todavía, pero da como resultado una herencia más limpia. –

+0

Lo probé y funciona ... Felicitaciones a ti @Chrisoph! ¡Esto es especialmente bueno porque funciona sin cambios para las clases derivadas también! –

+2

Esta solución funciona bastante bien. Gracias. –

4

Desafortunadamente solución de Christoph tal como está escrita sólo funciona en circunstancias muy limitadas . [EDITAR: como se comenta a continuación, ya no recuerdo mi razonamiento para esta frase y es probable que esté equivocado: "Tenga en cuenta que esto solo funcionará en clases abstractas, en primer lugar."] La siguiente dificultad es que g() solo funciona desde subclases DIRECTAS de A. Sin embargo, podemos solucionarlo:

private Class<?> extractClassFromType(Type t) throws ClassCastException { 
    if (t instanceof Class<?>) { 
     return (Class<?>)t; 
    } 
    return (Class<?>)((ParameterizedType)t).getRawType(); 
} 

public Class<B> g() throws ClassCastException { 
    Class<?> superClass = getClass(); // initial value 
    Type superType; 
    do { 
     superType = superClass.getGenericSuperclass(); 
     superClass = extractClassFromType(superType); 
    } while (! (superClass.equals(A.class))); 

    Type actualArg = ((ParameterizedType)superType).getActualTypeArguments()[0]; 
    return (Class<B>)extractClassFromType(actualArg); 
} 

Esto funcionará en muchas situaciones en la práctica, pero no TODO el tiempo. Considere lo siguiente:

public class Foo<U,T extends Collection<?>> extends A<T> {} 

(new Foo<String,List<Object>>() {}).g(); 

Esto lanzará un ClassCastException, porque el argumento de tipo aquí no es un Class o una ParameterizedType en absoluto; es el TypeVariableT. Así que ahora estarías atascado tratando de averiguar qué tipo se suponía que representaba el T, y así sucesivamente en el agujero del conejo.

Creo que la única respuesta razonable y general es algo parecido a la respuesta inicial de Nicolas: en general, si su clase necesita instanciar objetos de otra clase desconocida en tiempo de compilación, los usuarios de su clase deben pasar esa clase literal (o, tal vez, una fábrica) para su clase de forma explícita y no depender únicamente de los genéricos.

+0

La solución de Christoph funciona con clases no abstractas, la he probado e incluido mi trabajo en una respuesta aquí. –

+0

@ChrisNash Probablemente estés en lo cierto al respecto: han pasado cerca de 4 años desde que escribí esto, ya no escribo mucho Java, y tampoco me resulta claro lo que quise decir con el comentario de "clases abstractas". Todavía estoy de acuerdo con la corrección general de Nicolas y mis respuestas. Supongo que podría ser posible escribir un método que fue hasta el final "por el agujero del conejo", como dije, pero no estoy seguro de que me gustaría ... –

2

encuentro otro modo de obtener la clase del objeto genérico

public Class<?> getGenericClass(){ 
     Class<?> result =null; 
     Type type =this.getClass().getGenericSuperclass(); 

     if(type instanceofParameterizedType){ 
       ParameterizedType pt =(ParameterizedType) type; 
       Type[] fieldArgTypes = pt.getActualTypeArguments(); 
       result =(Class<?>) fieldArgTypes[0]; 
     } 
     return result; 
    } 
+0

Esto no parece funcionar. devuelve nulo cada vez –

+1

Bueno, ahora sé por qué sucede esto, esto solo funciona para clases abstractas que requieren la definición de implementación, e incluso de lo que se requiere para definir el tipo T en la implementación y no puede permitir la configuración de este valor durante el uso de la nueva clase. no funcionará en ciertos casos –

0

T puede resolverse con bastante facilidad utilizando TypeTools:

Class<T> t = (Class<T>) TypeResolver.resolveRawArguments(
           MyGenericClass.class, getClass()); 
+0

Esto no se compila, debido a "Error con MyGenericClass.class ... no se puede tomar el literal de clase de una variable de tipo". –

0

voy a elaborar una solución de Christoph.

Aquí es la clase abstracta ClassGetter:

private abstract class ClassGetter<T> { 
    public final Class<T> get() { 
     final ParameterizedType superclass = (ParameterizedType) 
      getClass().getGenericSuperclass(); 
     return (Class<T>)superclass.getActualTypeArguments()[0]; 
    } 
} 

Aquí es un método estático que utiliza la clase de arriba para encontrar una clase genérica de tipo:

public static <T> Class<T> getGenericClass() { 
    return new ClassGetter<T>() {}.get(); 
} 

Como ejemplo de su uso, usted podría hacer este método:

public static final <T> T instantiate() { 
    final Class<T> clazz = getGenericClass(); 
    try { 
     return clazz.getConstructor((Class[])null).newInstance(null); 
    } catch (Exception e) { 
     return null; 
    } 
} 

y luego usarlo como esto:

T var = instantiate(); 
+0

Este código no funciona. Lanza 'java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl no se puede convertir a java.lang.Class'. – Radiodef

+0

¿Cuál es el tipo de la "T" genérica que está utilizando? –

Cuestiones relacionadas