Aquí hay una solución mejorada, basada en ParameterizedType.getActualTypeArguments, ya mencionada por @noah, @Lars Bohl y algunos otros.
Primera pequeña mejora en la implementación. La fábrica no debe devolver la instancia, sino un tipo. Tan pronto como devuelva la instancia utilizando Class.newInstance(), reduce el alcance de uso. Porque solo los constructores sin argumentos pueden ser invocados de esta manera. Una mejor manera es devolver un tipo y permitir que un cliente elija qué constructor desea invocar:
public class TypeReference<T> {
public Class<T> type(){
try {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
throw new IllegalStateException("Could not define type");
}
if (pt.getActualTypeArguments().length != 1){
throw new IllegalStateException("More than one type has been found");
}
Type type = pt.getActualTypeArguments()[0];
String typeAsString = type.getTypeName();
return (Class<T>) Class.forName(typeAsString);
} catch (Exception e){
throw new IllegalStateException("Could not identify type", e);
}
}
}
Aquí hay algunos ejemplos de uso. @Lars Bohl ha mostrado solo una forma signe de obtener la genificación reificada a través de la extensión. @noah solo mediante la creación de una instancia con {}. Aquí hay pruebas para demostrar ambos casos:
import java.lang.reflect.Constructor;
public class TypeReferenceTest {
private static final String NAME = "Peter";
private static class Person{
final String name;
Person(String name) {
this.name = name;
}
}
@Test
public void erased() {
TypeReference<Person> p = new TypeReference<>();
Assert.assertNotNull(p);
try {
p.type();
Assert.fail();
} catch (Exception e){
Assert.assertEquals("Could not identify type", e.getMessage());
}
}
@Test
public void reified() throws Exception {
TypeReference<Person> p = new TypeReference<Person>(){};
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
static class TypeReferencePerson extends TypeReference<Person>{}
@Test
public void reifiedExtenension() throws Exception {
TypeReference<Person> p = new TypeReferencePerson();
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
}
Nota: se puede obligar a los clientes de TypeReferencesiempre uso {}cuando instancia se crea al hacer esta clase abstracta: public abstract class TypeReference<T>. No lo he hecho, solo para mostrar el caso de prueba borrado.