Ist es möglich, eine Instanz eines generischen Typs in Java zu erstellen? Ich denke basierend auf dem, was ich gesehen habe, dass die Antwort no
( aufgrund der Typlöschung ) ist, aber ich wäre interessiert, wenn jemand etwas sehen kann, das mir fehlt:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
BEARBEITEN: Es stellt sich heraus, dass Super Type Tokens verwendet werden könnten, um mein Problem zu beheben, aber es erfordert viel reflexionsbasierten Code, wie einige der folgenden Antworten gezeigt haben.
Ich werde dies für eine Weile offen lassen, um zu sehen, ob sich jemand etwas dramatisch anderes einfallen lässt als Ian Robertsons Artima-Artikel .
Antworten:
Du hast Recht. Das kannst du nicht machen
new E()
. Aber Sie können es ändernEs ist nervig. Aber es funktioniert. Das Einwickeln in das Werksmuster macht es etwas erträglicher.
quelle
Class<?>
Referenz mit Guava und TypeToken übergeben werden muss. Code und Links finden Sie in dieser Antwort!Keine Ahnung, ob dies hilft, aber wenn Sie einen generischen Typ in eine Unterklasse (einschließlich anonym) unterteilen, sind die Typinformationen über Reflection verfügbar. z.B,
Wenn Sie also Foo unterordnen, erhalten Sie eine Instanz von Bar, z.
Aber es ist viel Arbeit und funktioniert nur für Unterklassen. Kann aber praktisch sein.
quelle
Foo
nicht abstrakt ist. Aber warum funktioniert es nur in anonymen Unterklassen von Foo? Angenommen, wir machenFoo
konkret (wir lassen es wegabstract
), warum wirdnew Foo<Bar>();
dies zu einem Fehler führen, währendnew Foo<Bar>(){};
dies nicht der Fall ist? (Ausnahme: "Klasse kann nicht in ParameterizedType umgewandelt werden")<E>
Inclass Foo<E>
ist nicht an einen bestimmten Typ gebunden. Sie das außergewöhnliche Verhalten sehen , wennE
nicht ist statisch gebunden, wie in:new Foo<Bar>()
,new Foo<T>() {...}
oderclass Fizz <E> extends Foo<E>
. Der erste Fall ist nicht statisch gebunden, sondern wird beim Kompilieren gelöscht . Der zweite Fall ersetzt eine andere Typvariable (T) anstelle vonE
, ist aber noch nicht gebunden. Und im letzten Fall sollte es offensichtlich sein, dassE
noch ungebunden ist.class Fizz extends Foo<Bar>
- in diesem Fall erhalten Benutzer vonFizz
etwas, das a istFoo<Bar>
und nichts anderes als a sein kannFoo<Bar>
. In diesem Fall kodiert der Compiler diese Informationen gerne in die KlassenmetadatenFizz
und stellt sie alsParameterizedType
Reflection-Code zur Verfügung. Wenn Sie eine anonyme innere Klasse erstellen, als würdenew Foo<Bar>() {...}
sie dasselbe tun, außer dassFizz
der Compiler stattdessen einen "anonymen" Klassennamen generiert, den Sie erst kennen, wenn die äußere Klasse kompiliert ist.Foo<Bar<Baz>>
. Sie erstellen eine Instanz,ParameterizedTypeImpl
die nicht explizit erstellt werden kann. Daher ist es eine gute Idee zu überprüfen, ob a zurückgegebengetActualTypeArguments()[0]
wirdParameterizedType
. Wenn dies der Fall ist, möchten Sie den Rohtyp abrufen und stattdessen eine Instanz davon erstellen.In Java 8 können Sie die
Supplier
Funktionsschnittstelle verwenden, um dies ziemlich einfach zu erreichen:Sie würden diese Klasse folgendermaßen konstruieren:
Die Syntax
String::new
in dieser Zeile ist eine Konstruktorreferenz .Wenn Ihr Konstruktor Argumente akzeptiert, können Sie stattdessen einen Lambda-Ausdruck verwenden:
quelle
SomeContainer stringContainer = new SomeContainer(String::new);
?Sie benötigen eine abstrakte Fabrik der einen oder anderen Art, an die Sie das Geld weitergeben können:
quelle
Factory<>
ist eine Schnittstelle und es gibt keinen Körper. Der Punkt ist, dass Sie eine Schicht von Indirektionen benötigen, um das Geld an Methoden zu übergeben, die den erforderlichen Code zum Erstellen einer Instanz "kennen". Es ist viel besser, dies mit normalem Code zu tun, als mit MetalinguistikClass
oderConstructor
weil Reflexion eine ganze Welt voller Verletzungen mit sich bringt.SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
quelle
class GenericHome<T> extends Home<T>{}
Wenn Sie eine neue Instanz eines Typarguments in einer generischen Klasse benötigen, lassen Sie Ihre Konstruktoren ihre Klasse anfordern ...
Verwendungszweck:
Vorteile:
Nachteile:
Foo<L>
Beweis. Für Starter...newInstance()
wird ein Wobbler ausgelöst, wenn die Typargumentklasse keinen Standardkonstruktor hat. Dies gilt jedoch ohnehin für alle bekannten Lösungen.quelle
Sie können dies jetzt tun und es ist kein Reflexionscode erforderlich.
Natürlich, wenn Sie den Konstruktor aufrufen müssen, der einige Überlegungen erfordert, aber das ist sehr gut dokumentiert, ist dieser Trick nicht!
Hier ist das JavaDoc für TypeToken .
quelle
Denken Sie an einen funktionaleren Ansatz: Anstatt ein E aus dem Nichts zu erstellen (was eindeutig ein Codegeruch ist), übergeben Sie eine Funktion, die weiß, wie man eines erstellt, d. H.
quelle
Exception
NutzungSupplier<E>
statt.Aus dem Java-Tutorial - Einschränkungen für Generika :
Instanzen von Typparametern können nicht erstellt werden
Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Fehler bei der Kompilierung:
Um dieses Problem zu umgehen, können Sie durch Reflektion ein Objekt eines Typparameters erstellen:
Sie können die Append-Methode wie folgt aufrufen:
quelle
Hier ist eine Option, die ich mir ausgedacht habe. Sie kann helfen:
BEARBEITEN: Alternativ können Sie diesen Konstruktor verwenden (er erfordert jedoch eine Instanz von E):
quelle
Wenn Sie den Klassennamen während der Instanziierung nicht zweimal eingeben möchten, wie in:
Sie können die Factory-Methode verwenden:
Wie in:
quelle
Java erlaubt leider nicht, was Sie tun möchten. Siehe die offizielle Problemumgehung :
quelle
Wenn Sie zur Kompilierungszeit mit E arbeiten, ist Ihnen der tatsächliche generische Typ "E" nicht wirklich wichtig (entweder verwenden Sie Reflection oder arbeiten mit einer Basisklasse vom generischen Typ). Lassen Sie also die Unterklasse die Instanz von E bereitstellen.
quelle
Sie können verwenden:
Sie müssen jedoch den genauen Klassennamen angeben, einschließlich der Pakete, z.
java.io.FileInputStream
. Ich habe dies verwendet, um einen Parser für mathematische Ausdrücke zu erstellen.quelle
foo.getClass().getName()
. Woher kommt DIESE Instanz? Ich übergebe derzeit einen an einen Konstruktor in dem Projekt, an dem ich gerade arbeite.Hoffe das ist nicht zu spät um zu helfen !!!
Java ist Typensicherheit, nur Object kann eine Instanz erstellen.
In meinem Fall kann ich keine Parameter an die
createContents
Methode übergeben. Meine Lösung besteht darin, anstelle aller folgenden Antworten Erweiterungen zu verwenden.Dies ist mein Beispielfall, bei dem ich keine Parameter übergeben kann.
Wenn Sie Reflection verwenden, wird ein Laufzeitfehler erstellt, wenn Sie Ihre generische Klasse ohne Objekttyp erweitern. Um Ihren generischen Typ auf Objekt zu erweitern, konvertieren Sie diesen Fehler in einen Kompilierungszeitfehler.
quelle
Verwenden Sie die
TypeToken<T>
Klasse:quelle
(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Ich dachte, ich könnte das tun, war aber ziemlich enttäuscht: Es funktioniert nicht, aber ich denke, es lohnt sich immer noch, es zu teilen.
Vielleicht kann jemand korrigieren:
Es produziert:
Zeile 26 ist die mit der
[*]
.Die einzig praktikable Lösung ist die von @JustinRudd
quelle
Eine Verbesserung der Antwort von @ Noah.
Änderungsgrund
a] Ist sicherer, wenn mehr als 1 generischer Typ verwendet wird, falls Sie die Reihenfolge geändert haben.
b] Die Signatur eines generischen Klassentyps ändert sich von Zeit zu Zeit, sodass Sie nicht von unerklärlichen Ausnahmen in der Laufzeit überrascht werden.
Robuster Code
Oder verwenden Sie den Einzeiler
Einzeiliger Code
quelle
was Sie tun können ist -
Deklarieren Sie zuerst die Variable dieser generischen Klasse
2. Machen Sie dann einen Konstruktor daraus und instanziieren Sie das Objekt
Verwenden Sie es dann, wo immer Sie es verwenden möchten
Beispiel-
1
2
3.
quelle
Wie Sie sagten, können Sie dies aufgrund der Typlöschung nicht wirklich tun. Sie können dies mithilfe von Reflection tun, es erfordert jedoch viel Code und viel Fehlerbehandlung.
quelle
Wenn du meinst,
new E()
dann ist es unmöglich. Und ich würde hinzufügen, dass es nicht immer richtig ist - woher weißt du, ob E einen öffentlichen Konstruktor ohne Argumente hat? Sie können die Erstellung jedoch jederzeit an eine andere Klasse delegieren, die weiß, wie eine Instanz erstellt wird - dies kann auchClass<E>
Ihr benutzerdefinierter Code seinquelle
quelle
SomeContainer
ist einfachObject
. Gibt daherthis.getClass().getGenericSuperclass()
aClass
(Klasse java.lang.Object) zurück, nicht aParameterizedType
. Dies wurde auch bereits von Peer-Antwort stackoverflow.com/questions/75175/… hervorgehoben .Sie können dies mit dem folgenden Snippet erreichen:
quelle
Es gibt verschiedene Bibliotheken, die
E
mit ähnlichen Techniken wie im Robertson-Artikel für Sie aufgelöst werden können. Hier ist eine ImplementierungcreateContents
, die TypeTools verwendet , um die durch E dargestellte Rohklasse aufzulösen:Dies setzt voraus, dass getClass () in eine Unterklasse von SomeContainer aufgelöst wird und andernfalls fehlschlägt, da der tatsächlich parametrisierte Wert von E zur Laufzeit gelöscht wurde, wenn er nicht in einer Unterklasse erfasst wurde.
quelle
Hier ist eine Implementierung
createContents
, die TypeTools verwendet , um die durchE
folgende Klasse dargestellte Rohklasse aufzulösen :Dieser Ansatz funktioniert nur, wenn er
SomeContainer
in Unterklassen unterteilt ist, sodass der tatsächliche Wert vonE
in einer Typdefinition erfasst wird:Andernfalls wird der Wert von E zur Laufzeit gelöscht und kann nicht wiederhergestellt werden.
quelle
Sie können mit einem Klassenladeprogramm und dem Klassennamen eventuell einige Parameter festlegen.
quelle
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: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: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.quelle