Recuperación de Tipo Runtime en Clases Genericas de Java Mediante Reflexión

A continuación se exponen dos estrategias técnicas para resolver esta limitación:

Opción 1: Pasada Explícita de Referencia de Clase

La forma más directa consiste en pasar explícitamente el objeto Class<T> al método que requiere enstanciar el objeto. Dado que el compilador borra el tipo genérico al compilar, la clase herramienta interna no puede deducir qué tipo concreto representa T sin esa ayuda externa.

public class DynamicFactory<T> {
    public T constructInstance(Class<T> targetClass, String inputData) 
            throws Exception {
        return targetClass.getDeclaredConstructor().newInstance();
    }
}

Para utilizar este patrón, se debe definir una entidad concreta. Por ejemplo, una clase que representa un registro de perfil de usuario.

public class UserProfile {
    private String username;
    
    public void renderInfo() {
        System.out.println("Perfil renderizado");
    }
}

El uso correcto implica llamar a la fábrica proporcionando el tipo de clase objetivo:

public class ExecutionPoint {
    public static void main(String[] args) throws Exception {
        DynamicFactory<UserProfile> factory = new DynamicFactory<>();
        UserProfile entity = factory.constructInstance(UserProfile.class, "");
        entity.renderInfo();
    }
}

¿Por qué no se puede simplemente usar T.class? Porque el tipo genérico se convierte en Object después del ensamblaje. Aunque técnicamente se podría omitir el genérico y retornar Object, esto obligaría a realizar castings manuales en el código cliente, perdiendo seguridad tipológica.

// Alternativa sin genéricos pero menos segura
public Object constructRaw(Class tClass) {
    return tClass.newInstance();
}

Opción 2: Patrón de Herencia con Reflexión

Si bien el primer método es simple, requiere repetir la llamada de Class en cada instancia. Existe una técnica avanzada que permite recuperar la información del tipo desde la clase raíz utilizando reflexión, siempre que se establezca una jerarquía donde las subclases concretes fijen el tipo.

  1. Se define una clase abstracta genérica que actuará como la lógica central.
  2. Dentro de esta clase, se implementa la lógica para obtener ParameterizedType del superclase.
  3. Se crean clases concretas que extiendan la genérica especificando el tipo real en el parámetro.

Esta estructura permite que el objeto instancie su propia referencia de tipo correcta internamente, eliminando la necesidad de pasar Class como argumento en la llamada al método.

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

public abstract class BaseHandler<T> {
    public T getCreatedInstance() throws Exception {
        Type superclass = getClass().getGenericSuperclass();
        if (!(superclass instanceof ParameterizedType)) {
             throw new IllegalArgumentException("Superclass debe ser parametrizado");
        }
        ParameterizedType pType = (ParameterizedType) superclass;
        Type[] actualTypes = pType.getActualTypeArguments();
        
        @SuppressWarnings("unchecked")
        Class<T> clazz = (Class<T>) actualTypes[0];
        
        return clazz.getDeclaredConstructor().newInstance();
    }
}

Luego, creamos una implementación específica que fija el tipo.

public class HandlerForProfile extends BaseHandler<UserProfile> {
    // No requiere código adicional, la lógica está en la clase padre
}

Finalmente, en el flujo principal, la instancia se crea automáticamente gracias a la definición heredada.

public class RunTest {
    public static void main(String[] args) throws Exception {
        HandlerForProfile handler = new HandlerForProfile();
        UserProfile data = handler.getCreatedInstance();
        data.renderInfo();
    }
}

Etiquetas: java generics Reflection type-erasure design-patterns

Publicado el 9-25 22:24