Voici une solution améliorée, basée sur ParameterizedType.getActualTypeArguments
, déjà mentionnée par @noah, @Lars Bohl et quelques autres.
Première petite amélioration de l'implémentation. Factory ne doit pas renvoyer d'instance, mais un type. Dès que vous retournez une instance en utilisant, Class.newInstance()
vous réduisez la portée de l'utilisation. Parce que seuls les constructeurs sans arguments peuvent être invoqués comme ceci. Une meilleure façon est de renvoyer un type et de permettre au client de choisir le constructeur qu'il souhaite invoquer:
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);
}
}
}
Voici quelques exemples d'utilisation. @Lars Bohl a montré seulement un moyen de signe pour obtenir générique réifié via l'extension. @noah uniquement via la création d'une instance avec {}
. Voici des tests pour démontrer les deux cas:
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);
}
}
Remarque: vous pouvez forcer les clients de TypeReference
toujours utiliser {}
lorsque l' instance est créée en faisant cette classe abstraite: public abstract class TypeReference<T>
. Je ne l'ai pas fait, seulement pour montrer le cas de test effacé.