Hier ist eine verbesserte Lösung, die auf ParameterizedType.getActualTypeArguments
wurde bereits von @noah, @Lars Bohl und einigen anderen erwähnt.
Erste kleine Verbesserung bei der Umsetzung. Factory sollte keine Instanz zurückgeben, sondern einen Typ. Sobald Sie eine Instanz zurückgeben, indem Sie Class.newInstance()
reduzieren Sie einen Nutzungsumfang. Denn nur Konstruktoren ohne Argumente können auf diese Weise aufgerufen werden. Ein besserer Weg ist es, einen Typ zurückzugeben und dem Kunden die Wahl zu lassen, welchen Konstruktor 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 ist ein Beispiel für die Verwendung. @Lars Bohl hat nur einen einzigen Weg gezeigt, wie man verifizierte Genetiken über eine Erweiterung erhält. @noah nur über das Erzeugen einer Instanz mit {}
. Hier sind Tests, die beide Fälle 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);
}
}
Nota: können Sie die Clients von TypeReference
immer verwenden {}
wenn eine Instanz erstellt wird, indem diese Klasse abstrakt gemacht wird: public abstract class TypeReference<T>
. Ich habe es nicht getan, nur um den gelöschten Testfall zu zeigen.
3 Stimmen
Habe gerade die Leistung auf einem Android-Gerät getestet. 10000 Operationen und: 8-9 ms dauert new SomeClass(), 9-11 ms dauert Factory<SomeClass>.createInstance() und 64-71 ms dauert shortest reflection: SomeClass z = SomeClass.class.newInstance(). Und alle Tests waren in einem einzigen try-catch-Block. Reflection newInstance() wirft 4 verschiedene Ausnahmen, erinnern Sie sich? Also habe ich beschlossen, das Factory-Muster zu verwenden
0 Stimmen
Siehe auch: stackoverflow.com/a/5684761/59087
4 Stimmen
Mit Java 8 können Sie nun eine Konstruktorreferenz oder ein Lambda übergeben, wodurch dieses Problem ziemlich einfach zu umgehen ist. Siehe meine Antwort unten für Einzelheiten.
0 Stimmen
Ich halte es für eine schlechte Idee, solchen Code zu schreiben, es gibt elegantere und lesbarere Wege, das darunter liegende Problem zu lösen.
2 Stimmen
@DavidCitron "für eine kurze Zeit" sagte er... Seitdem sind elf Jahre vergangen...