Aufgrund der Implementierung von Java-Generika können Sie keinen Code wie diesen haben:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Wie kann ich dies unter Wahrung der Typensicherheit implementieren?
Ich habe in den Java-Foren eine Lösung gesehen, die so aussieht:
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
Aber ich verstehe wirklich nicht, was los ist.
java
arrays
generics
reflection
instantiation
Tatsuhirosatou
quelle
quelle
Antworten:
Ich muss im Gegenzug eine Frage stellen: Ist Ihr
GenSet
"markiert" oder "nicht markiert"? Was bedeutet das?Aktiviert : starkes Tippen .
GenSet
weiß explizit, welchen Objekttyp es enthält (dh sein Konstruktor wurde explizit mit einemClass<E>
Argument aufgerufen , und Methoden lösen eine Ausnahme aus, wenn ihnen Argumente übergeben werden, die nicht vom Typ sindE
. SieheCollections.checkedCollection
.-> in diesem Fall sollten Sie schreiben:
Deaktiviert : schwache Eingabe . Für keines der als Argument übergebenen Objekte wird tatsächlich eine Typprüfung durchgeführt.
-> in diesem Fall sollten Sie schreiben
Beachten Sie, dass der Komponententyp des Arrays das Löschen des Typparameters sein sollte:
All dies resultiert aus einer bekannten und absichtlichen Schwäche von Generika in Java: Es wurde mithilfe von Erasure implementiert, sodass "generische" Klassen nicht wissen, mit welchem Typargument sie zur Laufzeit erstellt wurden, und daher keine Typ- liefern können. Sicherheit, es sei denn, ein expliziter Mechanismus (Typprüfung) ist implementiert.
quelle
Object[] EMPTY_ELEMENTDATA = {}
zur Speicherung haben. Kann ich diesen Mechanismus verwenden, um die Größe zu ändern, ohne den Typ mithilfe von Generika zu kennen?public void <T> T[] newArray(Class<T> type, int length) { ... }
Du kannst das:
Dies ist eine der vorgeschlagenen Möglichkeiten zum Implementieren einer generischen Sammlung in Effective Java. Punkt 26 . Keine Tippfehler, keine Notwendigkeit, das Array wiederholt umzuwandeln. Dies löst jedoch eine Warnung aus, da dies möglicherweise gefährlich ist und mit Vorsicht verwendet werden sollte. Wie in den Kommentaren ausführlich beschrieben, tarnt sich dies
Object[]
nun als unserE[]
Typ und kann unerwartete Fehler verursachen oderClassCastException
, tarnt sich bei unsicherer Verwendung verursachen.Als Faustregel gilt, dass dieses Verhalten sicher ist, solange das Cast-Array intern verwendet wird (z. B. zum Sichern einer Datenstruktur) und nicht zurückgegeben oder dem Client-Code ausgesetzt wird. Wenn Sie ein Array eines generischen Typs an einen anderen Code zurückgeben müssen, ist die von
Array
Ihnen erwähnte Reflektionsklasse der richtige Weg.Erwähnenswert ist, dass Sie, wo immer möglich, viel glücklicher mit
List
s als mit Arrays arbeiten, wenn Sie Generika verwenden. Sicherlich haben Sie manchmal keine Wahl, aber die Verwendung des Sammlungsframeworks ist weitaus robuster.quelle
String[] s=b;
in der obigentest()
Methode. Das liegt daran, dass das Array von E nicht wirklich ist, sondern Object []. Dies ist wichtig, wenn Sie möchten, z. B. aList<String>[]
- Sie können ein nicht dafür verwendenObject[]
, Sie müssen einList[]
spezielles haben. Aus diesem Grund müssen Sie die reflektierte Array-Erstellung der Klasse <?> Verwenden.public E[] toArray() { return (E[])internalArray.clone(); }
wanninternalArray
als eingegebenE[]
wird und daher tatsächlich einObject[]
. Dies schlägt zur Laufzeit mit einer Typumwandlungsausnahme fehl, daObject[]
ein Array eines beliebigen Typs nicht zugewiesenE
werden kann.E[] b = (E[])new Object[1];
Sie deutlich sehen, dass der einzige Verweis auf das erstellte Array istb
und dass der Typ vonb
istE[]
. Daher besteht keine Gefahr, dass Sie versehentlich über eine andere Variable eines anderen Typs auf dasselbe Array zugreifen. Wenn Sie stattdessenObject[] a = new Object[1]; E[]b = (E[])a;
hätten, müssten Sie paranoid sein, wie Sie verwendena
.So verwenden Sie Generika, um ein Array mit genau dem Typ zu erhalten, nach dem Sie suchen, während die Typensicherheit erhalten bleibt (im Gegensatz zu den anderen Antworten, die Ihnen entweder ein
Object
Array zurückgeben oder beim Kompilieren zu Warnungen führen):Dies wird ohne Warnungen kompiliert, und wie Sie in sehen können
main
, können Sie für jeden Typ, für den Sie eine InstanzGenSet
als deklarierena
, einem Array dieses Typs zuweisen und ein Element aus zuweisena
zu einer Variable dieses Typs, dass das Array bedeutet und die Werte im Array sind vom richtigen Typ.Es funktioniert mit Klassenliteralen als Laufzeit-Token, wie in den Java-Tutorials beschrieben . Klassenliterale werden vom Compiler als Instanzen von behandelt
java.lang.Class
. Um eine zu verwenden, folgen Sie einfach dem Namen einer Klasse mit.class
. SoString.class
wirkt wie einClass
Objekt der Klasse darstelltString
. Dies funktioniert auch für Schnittstellen, Aufzählungen, beliebige dimensionale Arrays (z. B.String[].class
) , Grundelemente (z. B.int.class
) und das Schlüsselwortvoid
(dhvoid.class
).Class
selbst ist generisch (deklariert alsClass<T>
, wobeiT
für den Typ steht, den dasClass
Objekt darstellt), was bedeutet, dass der Typ vonString.class
istClass<String>
.Wenn Sie also den Konstruktor für aufrufen
GenSet
, übergeben Sie ein Klassenliteral für das erste Argument, das ein Array desGenSet
deklarierten Typs der Instanz darstellt (z. B.String[].class
fürGenSet<String>
. ). Beachten Sie, dass Sie kein Array von Grundelementen abrufen können, da Grundelemente nicht für Typvariablen verwendet werden können.Innerhalb des Konstruktors gibt der Aufruf der Methode
cast
das übergebeneObject
Argument an die Klasse zurück, die durch dasClass
Objekt dargestellt wird, für das die Methode aufgerufen wurde. Das Aufrufen der statischen MethodenewInstance
injava.lang.reflect.Array
gibt alsObject
Array den Typ zurück, der durch dasClass
als erstes Argument übergebene Objekt und die durch dasint
als zweites Argument übergebene Länge angegebene Länge dargestellt wird . Der Aufruf der MethodegetComponentType
Gibt einClass
Objekt den Komponententyp des Arrays von dem dargestellten darstelltClass
Objekt , auf dem das Verfahren aufgerufen wurde (zBString.class
für dieString[].class
,null
wenn dieClass
Objekt nicht ein Array darstellt).Dieser letzte Satz ist nicht ganz richtig. Beim Aufrufen wird
String[].class.getComponentType()
einClass
Objekt zurückgegeben, das die Klasse darstelltString
, sein Typ jedochClass<?>
nichtClass<String>
. Aus diesem Grund können Sie Folgendes nicht ausführen.Gleiches gilt für jede Methode
Class
, die einClass
Objekt zurückgibt .In Bezug auf Joachim Sauers Kommentar zu dieser Antwort (ich habe nicht genug Ruf, um sie selbst zu kommentieren) führt das Beispiel, in dem die Besetzung verwendet
T[]
wird, zu einer Warnung, da der Compiler in diesem Fall keine Typensicherheit garantieren kann.Bearbeiten Sie die Kommentare von Ingo:
quelle
Dies ist die einzige Antwort, die typsicher ist
quelle
Arrays#copyOf()
ist unabhängig von der Länge des Arrays, das als erstes Argument angegeben wurde. Das ist klug, obwohl es die Kosten für Anrufe anMath#min()
und zahltSystem#arrayCopy()
, von denen keines unbedingt erforderlich ist, um diese Aufgabe zu erledigen. docs.oracle.com/javase/7/docs/api/java/util/…E
es sich um eine Typvariable handelt. Die varargs erstellen ein Array zum Löschen vonE
whenE
als Typvariable, wodurch es sich nicht wesentlich von unterscheidet(E[])new Object[n]
. Weitere Informationen finden Sie unter http://ideone.com/T8xF91 . Es ist keineswegs typsicherer als jede andere Antwort.new E[]
. Das Problem, das Sie in Ihrem Beispiel gezeigt haben, ist ein allgemeines Löschproblem, das nicht nur für diese Frage und diese Antwort gilt.Um auf weitere Dimensionen zu erweitern, fügen Sie einfach
[]
's und Dimensionsparameter hinzunewInstance()
(T
ist ein Typparameter,cls
ist einClass<T>
,d1
bisd5
sind ganze Zahlen):Siehe
Array.newInstance()
für Details.quelle
In Java 8 können wir eine Art generische Array-Erstellung mit einem Lambda oder einer Methodenreferenz durchführen. Dies ähnelt dem Reflexionsansatz (der a passiert
Class
), aber hier verwenden wir keine Reflexion.Dies wird beispielsweise von verwendet
<A> A[] Stream.toArray(IntFunction<A[]>)
.Dies könnte auch vor Java 8 mit anonymen Klassen erfolgen, ist jedoch umständlicher.
quelle
ArraySupplier
. Sie können den Konstruktor als deklarierenGenSet(Supplier<E[]> supplier) { ...
und ihn mit derselben Zeile wie Sie aufrufen.IntFunction<E[]>
, aber ja das stimmt.Dies wird in Kapitel 5 (Generika) von Effective Java, 2. Ausgabe , Punkt 25 behandelt. Ziehen Sie Listen Arrays vor
Ihr Code funktioniert, generiert jedoch eine ungeprüfte Warnung (die Sie mit der folgenden Anmerkung unterdrücken können:
Es wäre jedoch wahrscheinlich besser, eine Liste anstelle eines Arrays zu verwenden.
Es gibt eine interessante Diskussion über diesen Fehler / diese Funktion auf der OpenJDK-Projektseite .
quelle
Sie müssen das Class-Argument nicht an den Konstruktor übergeben. Versuche dies.
und
Ergebnis:
quelle
Java-Generika überprüfen Typen zur Kompilierungszeit und fügen entsprechende Casts ein, löschen jedoch die Typen in den kompilierten Dateien. Dies macht generische Bibliotheken für Code verwendbar, der Generika nicht versteht (was eine bewusste Entwurfsentscheidung war), was jedoch bedeutet, dass Sie normalerweise nicht herausfinden können, um welchen Typ es sich zur Laufzeit handelt.
Der öffentliche
Stack(Class<T> clazz,int capacity)
Konstruktor erfordert , dass Sie ein Class - Objekt zur Laufzeit übergeben, die Mittel Klasseninformation ist zum Laufzeit - Code zur Verfügung , die es braucht. Und derClass<T>
Formular bedeutet, dass der Compiler prüft, ob das von Ihnen übergebene Klassenobjekt genau das Klassenobjekt für Typ T ist. Keine Unterklasse von T, keine Oberklasse von T, sondern genau T.Dies bedeutet dann, dass Sie in Ihrem Konstruktor ein Array-Objekt des entsprechenden Typs erstellen können. Dies bedeutet, dass der Typ der Objekte, die Sie in Ihrer Sammlung speichern, an dem Punkt überprüft wird, an dem sie der Sammlung hinzugefügt werden.
quelle
Hallo, obwohl der Thread tot ist, möchte ich Ihre Aufmerksamkeit darauf lenken:
Generics wird zur Typprüfung während der Kompilierungszeit verwendet:
Machen Sie sich keine Sorgen über das Schreiben von Typwarnungen, wenn Sie eine generische Klasse schreiben. Sorgen Sie sich, wenn Sie es verwenden.
quelle
Was ist mit dieser Lösung?
Es funktioniert und sieht zu einfach aus, um wahr zu sein. Gibt es einen Nachteil?
quelle
T[]
, können Sie nicht programmgesteuert eine Instanz erstellen , dieT[] elems
an die Funktion übergeben wird. Und wenn Sie könnten, würden Sie die Funktion nicht benötigen.Schauen Sie sich auch diesen Code an:
Es konvertiert eine Liste aller Arten von Objekten in ein Array desselben Typs.
quelle
List
mehr als eine Art von Objekt in es zBtoArray(Arrays.asList("abc", new Object()))
werfen wirdArrayStoreException
.for
Schleife und andere zu vermeiden, die ich verwendetArrays.fill(res, obj);
habe, wollte ich für jeden Index den gleichen Wert.Ich habe einen schnellen und einfachen Weg gefunden, der für mich funktioniert. Beachten Sie, dass ich dies nur unter Java JDK 8 verwendet habe. Ich weiß nicht, ob es mit früheren Versionen funktioniert.
Obwohl wir ein generisches Array eines bestimmten Typparameters nicht instanziieren können, können wir ein bereits erstelltes Array an einen generischen Klassenkonstruktor übergeben.
Jetzt können wir das Array hauptsächlich so erstellen:
Für mehr Flexibilität mit Ihren Arrays können Sie eine verknüpfte Liste verwenden, z. die ArrayList und andere Methoden in der Java.util.ArrayList-Klasse.
quelle
Das Beispiel verwendet Java Reflection, um ein Array zu erstellen. Dies wird im Allgemeinen nicht empfohlen, da es nicht typsicher ist. Verwenden Sie stattdessen einfach eine interne Liste und vermeiden Sie das Array überhaupt.
quelle
Übergeben einer Werteliste ...
quelle
Ich habe dieses Code-Snippet erstellt, um eine Klasse, die für ein einfaches automatisiertes Testdienstprogramm übergeben wird, reflektierend zu instanziieren.
Beachten Sie dieses Segment:
für Array-Initiierung, wobei Array.newInstance (Array-Klasse, Array-Größe) . Die Klasse kann sowohl primitiv (int.class) als auch objekt (Integer.class) sein.
BeanUtils ist Teil des Frühlings.
quelle
Eine einfachere Möglichkeit besteht darin, ein Array von Objekten zu erstellen und es wie im folgenden Beispiel in den gewünschten Typ umzuwandeln:
Dabei
SIZE
handelt es sich um eine Konstante undT
eine Typkennungquelle
Die von anderen Leuten vorgeschlagene Zwangsbesetzung hat bei mir nicht funktioniert, mit Ausnahme des illegalen Castings.
Diese implizite Besetzung funktionierte jedoch einwandfrei:
Dabei ist Item eine von mir definierte Klasse, die das Mitglied enthält:
Auf diese Weise erhalten Sie ein Array vom Typ K (wenn das Element nur den Wert hat) oder einen beliebigen generischen Typ, den Sie in der Klasse Element definieren möchten.
quelle
Niemand sonst hat die Frage beantwortet, was in dem von Ihnen geposteten Beispiel vor sich geht.
Wie andere gesagt haben, werden Generika während der Kompilierung "gelöscht". Zur Laufzeit weiß eine Instanz eines Generikums nicht, welchen Komponententyp sie hat. Der Grund dafür ist historisch. Sun wollte Generika hinzufügen, ohne die vorhandene Schnittstelle (sowohl Quelle als auch Binärdatei) zu beschädigen.
Arrays auf der anderen Seite do ihren Komponententyp zur Laufzeit kennen.
In diesem Beispiel wird das Problem umgangen, indem der Code, der den Konstruktor aufruft (der den Typ kennt), einen Parameter übergibt, der der Klasse den erforderlichen Typ mitteilt.
Die Anwendung würde also die Klasse mit so etwas wie konstruieren
und der Konstruktor weiß jetzt (zur Laufzeit), was der Komponententyp ist, und kann diese Informationen verwenden, um das Array über die Reflection-API zu erstellen.
Schließlich haben wir eine Typumwandlung, da der Compiler nicht wissen kann, dass das von zurückgegebene Array
Array#newInstance()
der richtige Typ ist (obwohl wir es wissen).Dieser Stil ist etwas hässlich, kann aber manchmal die am wenigsten schlechte Lösung für die Erstellung generischer Typen sein, die ihren Komponententyp zur Laufzeit aus irgendeinem Grund kennen müssen (Erstellen von Arrays oder Erstellen von Instanzen ihres Komponententyps usw.).
quelle
Ich habe eine Art Lösung für dieses Problem gefunden.
Die folgende Zeile löst einen generischen Fehler bei der Array-Erstellung aus
Wenn ich jedoch
List<Person>
in einer separaten Klasse kapsle, funktioniert es.Sie können Personen in der Klasse PersonList durch einen Getter aussetzen. Die folgende Zeile gibt Ihnen ein Array mit einem
List<Person>
in jedem Element ein enthält. Mit anderen Worten Array vonList<Person>
.Ich brauchte so etwas in einem Code, an dem ich arbeitete, und das habe ich getan, damit es funktioniert. Bisher keine Probleme.
quelle
Sie können ein Objektarray erstellen und es überall in E umwandeln. Ja, es ist nicht sehr sauber, aber es sollte zumindest funktionieren.
quelle
Versuche dies.
quelle
Element
Klasse?Eine einfache, wenn auch unübersichtliche Problemumgehung besteht darin, eine zweite "Inhaber" -Klasse in Ihrer Hauptklasse zu verschachteln und sie zum Speichern Ihrer Daten zu verwenden.
quelle
new Holder<Thing>[10]
ist eine generische Array-Erstellung.Vielleicht ohne Bezug zu dieser Frage, aber während ich den "
generic array creation
" Fehler für die Verwendung bekamIch finde die folgenden Arbeiten heraus (und habe für mich gearbeitet) mit
@SuppressWarnings({"unchecked"})
:quelle
Ich frage mich, ob dieser Code ein effektives generisches Array erstellen würde.
Bearbeiten: Vielleicht besteht eine alternative Möglichkeit zum Erstellen eines solchen Arrays, wenn die von Ihnen gewünschte Größe bekannt und klein wäre, darin, einfach die erforderliche Anzahl von "Nullen" in den Befehl zeroArray einzugeben?
Dies ist jedoch offensichtlich nicht so vielseitig wie die Verwendung des createArray-Codes.
quelle
T
wannT
eine Typvariable ist, dhzeroArray
gibt ein zurückObject[]
. Siehe http://ideone.com/T8xF91 .Sie könnten eine Besetzung verwenden:
quelle
a
außerhalb der Klasse aus!Ich habe tatsächlich eine ziemlich einzigartige Lösung gefunden, um die Unfähigkeit zu umgehen, ein generisches Array zu initiieren. Was Sie tun müssen, ist eine Klasse zu erstellen, die die generische Variable T wie folgt aufnimmt:
und dann in Ihrer Array-Klasse einfach so anfangen:
Das Starten von a
new Generic Invoker[]
führt zu einem Problem mit dem Kontrollkästchen "Nicht aktiviert", es sollten jedoch keine Probleme auftreten.Um aus dem Array zu gelangen, sollten Sie das Array [i] .variable wie folgt aufrufen:
Der Rest, z. B. die Größenänderung des Arrays, kann mit Arrays.copyOf () wie folgt ausgeführt werden:
Und die Add-Funktion kann folgendermaßen hinzugefügt werden:
quelle
T
, nicht eines Arrays eines parametrisierten Typs.Laut vnportnoy die Syntax
Erstellt ein Array von Nullreferenzen, die als gefüllt werden sollen
Das ist typsicher.
quelle
quelle
Die generische Array-Erstellung ist in Java nicht zulässig, aber Sie können dies wie folgt tun
quelle