Hier ist eine verbesserte Lösung, basierend auf ParameterizedType.getActualTypeArguments
@noah, @Lars Bohl und einigen anderen .
Erste kleine Verbesserung in der Implementierung. Factory sollte keine Instanz zurückgeben, sondern einen Typ. Sobald Sie die Instanz mit zurückgeben Class.newInstance()
, reduzieren Sie den Nutzungsumfang. Weil nur Konstruktoren ohne Argumente so aufgerufen werden können. Eine bessere Möglichkeit besteht darin, einen Typ zurückzugeben und einem Client die Auswahl des Konstruktors zu ermöglichen, den er aufrufen möchte:
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);
}
}
}
Hier sind Anwendungsbeispiele. @Lars Bohl hat nur einen Weg aufgezeigt, wie man durch Erweiterung reifizierter Genener wird. @noah nur durch Erstellen einer Instanz mit {}
. Hier sind Tests, um beide Fälle zu demonstrieren:
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);
}
}
Hinweis: Sie können die Clients zwingen, TypeReference
immer zu verwenden, {}
wenn eine Instanz erstellt wird, indem Sie diese Klasse abstrakt machen : public abstract class TypeReference<T>
. Ich habe es nicht getan, nur um einen gelöschten Testfall zu zeigen.