Angenommen, Sie haben eine Arrayliste, die wie folgt definiert ist:
ArrayList<String> someData = new ArrayList<>();
Später in Ihrem Code können Sie aufgrund von Generika Folgendes sagen:
String someLine = someData.get(0);
Und der Compiler weiß sofort, dass er einen String bekommt. Juhu Generika! Dies wird jedoch fehlschlagen:
String[] arrayOfData = someData.toArray();
toArray() gibt immer ein Array von Objekten zurück, nicht das generische, das definiert wurde. Warum tut die get(x) Methode weiß, was sie zurückgibt, aber toArray() standardmäßig auf Objekte?
Worüber redest du? Welche Klasse hat die toArray() Methode?
– Fisch
13. April 2016 um 12:52 Uhr
ArrayList hat eine toArray() Methode, aber selbst wenn Sie die generische, die definieren toArray() Methode wird zurückgegeben Object[]nicht E[]was mit dem Generikum impliziert wird.
– Kaninchentyp
13. April 2016 um 12:53 Uhr
Anstatt zu überschreiben toArray() Sie können verwenden toArray(T[] a) Methode, um ein geeignetes Array zu erhalten.
– nur ein wenig
13. April 2016 um 12:54 Uhr
Ja, ich weiß, ich kann toArray(T[] a)aber warum wurde es nicht einfach direkt eingebaut toArray(). Ich verstehe nicht warum get(x) weiß, was es ausgibt, aber toArray() nicht.
– Kaninchentyp
13. April 2016 um 12:56 Uhr
@JimmyB: Eine Alternative ist eine Art Fabrik für den dynamischen Typ, z Java 8 Stream.toArray Verwendet IntFunction<ArrayType> als Fabriktyp, als Parameter übergeben. Der Grund, warum dieses Muster bisher nicht verwendet wurde, zB für die Collection Schnittstelle, ist, dass nur Java 8 es erlaubt, es so ordentlich zu implementieren ElementType[]::newermöglicht z String[] array = stream.toArray(String[]::new)…
– Holger
13. April 2016 um 15:45 Uhr
nur ein wenig
Schaut man sich die Umsetzung an toArray(T[] a) von ArrayList Klasse, es ist wie:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
Das Problem bei dieser Methode ist, dass Sie ein Array desselben generischen Typs übergeben müssen. Überlegen Sie nun, ob diese Methode kein Argument akzeptiert, dann wäre die Implementierung ähnlich wie:
public <T> T[] toArray() {
T[] t = new T[size]; // compilation error
return Arrays.copyOf(elementData, size, t.getClass());
}
Aber das Problem hier ist das Sie können in Java keine generischen Arrays erstellen weil der Compiler nicht genau weiß, was T repräsentiert. Mit anderen Worten Erstellung eines Arrays eines nicht verifizierbaren Typs (JLS §4.7) ist in Java nicht erlaubt.
Ein weiteres wichtiges Zitat von Array Store-Ausnahme (JLS §10.5):
Wenn der Komponententyp eines Arrays nicht reifizierbar wäre (§4.7), könnte die Java Virtual Machine die im vorhergehenden Absatz beschriebene Speicherprüfung nicht durchführen. Aus diesem Grund ist ein Array-Erstellungsausdruck mit einem nicht verifizierbaren Elementtyp verboten (§15.10.1).
Aus diesem Grund hat Java eine überladene Version bereitgestellt toArray(T[] a).
Ich werde die Methode toArray() überschreiben, um ihr mitzuteilen, dass sie ein Array von E zurückgeben wird.
Also statt überschreiben toArray()du solltest benutzen toArray(T[] a).
Vielen Dank. Das erklärt schwarz auf weiß, warum es nicht funktioniert.
– Kaninchentyp
13. April 2016 um 13:11 Uhr
Kurz gesagt, Java lässt keine Methode zu polymorph (dh generisch) in (nur) seinem Rückgabewert. Ein Beispiel für eine Sprache, die tut Unterstützen Sie dies ist Haskell.
– jpaugh
13. April 2016 um 21:28 Uhr
Ich denke, Sie haben den Kern des Problems erfasst, nämlich dass Java keine Arrays generischer Typen erstellen kann.
– Tempo
14. April 2016 um 1:49 Uhr
@jpaugh Java erlaubt das und leitet sogar den Typ der RHS einer Zuweisung basierend auf der LHS ab. Sie können die Typparameter auch explizit deklarieren, wenn Sie eine generische Methode aufrufen. Siehe zum Beispiel ideone.com/AObtRR.
– JohannesD
14. April 2016 um 10:39 Uhr
@JohannesD Ich stehe korrigiert. (Danke!) Beispiele wie toArray und dein link zu getnull zeigen, dass Java kommt so nah, und verfehlt nur das Ziel. Andererseits versucht Haskell nicht einmal, Subtypen zu erstellen, daher ist es schwierig, ihre Generika-Unterstützung eins zu eins zu vergleichen.
– jpaugh
14. April 2016 um 19:09 Uhr
AdamSkywalker
Allgemeine Informationen sind gelöscht zur Laufzeit. JVM weiß nicht, ob Ihre Liste ist List<String> oder List<Integer> (zur Laufzeit T in List<T> wird als aufgelöst Object), also ist der einzig mögliche Array-Typ Object[].
Sie können verwenden toArray(T[] array) obwohl – in diesem Fall kann JVM die Klasse eines bestimmten Arrays verwenden, Sie können es in der sehen ArrayList Implementierung:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
Ihre Antwort ist richtig, aber für Nicht-Java-Experten nicht leicht zu verstehen. Wichtig ist, dass das zurückgegebene Array zur Laufzeit erstellt werden muss (-> fehlende Info welchen Typs), wo als get(..) gibt nur ein vorhandenes Objekt zurück.
Tatsächlich gibt das Javadoc sogar ein Beispiel dafür, wie Sie genau das tun können, was Sie tun möchten:
String[] y = x.toArray(new String[0]);
Wichtig ist, dass Arrays in Java ihren Komponententyp zur Laufzeit kennen. String[] und Integer[] sind zur Laufzeit verschiedene Klassen, und Sie können Arrays zur Laufzeit nach ihrem Komponententyp fragen. Daher wird zur Laufzeit ein Komponententyp benötigt (entweder durch Hartcodierung eines verifizierbaren Komponententyps zur Kompilierzeit mit new String[...]oder verwenden Array.newInstance() und Übergeben eines Klassenobjekts), um ein Array zu erstellen.
Andererseits existieren Typargumente in Generika zur Laufzeit nicht. Es gibt absolut keinen Unterschied zur Laufzeit zwischen an ArrayList<String> und ein ArrayList<Integer>. Es ist alles gerecht ArrayList.
Das ist der grundlegende Grund, warum Sie nicht einfach eine nehmen können List<String> und bekomme ein String[] ohne den Komponententyp irgendwie separat zu übergeben – Sie müssten Informationen zum Komponententyp aus etwas herausholen, das keine Informationen zum Komponententyp enthält. Das ist natürlich unmöglich.
Senseiwu
Ich kann und werde manchmal einen Iterator verwenden, anstatt ein Array zu erstellen, aber das kam mir immer seltsam vor. Warum weiß die get(x)-Methode, was sie zurückgibt, aber toArray() verwendet standardmäßig Objects? Es ist wie auf halbem Weg beim Entwerfen, als sie entschieden haben, dass dies hier nicht benötigt wird?
Da es bei der Frage anscheinend nicht nur darum geht, sich mit der Verwendung fortzubewegen toArray() mit Generika, sondern auch um das Verständnis der Gestaltung der Methoden in der ArrayList Klasse möchte ich ergänzen:
ArrayList ist eine generische Klasse, da sie wie deklariert ist
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
wodurch es möglich ist, generische Methoden wie z public E get(int index) innerhalb der Klasse.
Aber wenn eine Methode wie z toArray() kehrt nicht zurück Eeher E[] dann wird es etwas knifflig. Es wäre nicht möglich, eine Signatur wie z public <E> E[] toArray() weil es nicht möglich ist, generische Arrays zu erstellen.
Die Erstellung von Arrays erfolgt zur Laufzeit und aufgrund von Löschen eingebendie Java-Laufzeit hat keine spezifischen Informationen des Typs, der durch dargestellt wird E. Die einzige Problemumgehung besteht derzeit darin, den erforderlichen Typ als Parameter an die Methode und damit die Signatur zu übergeben public <T> T[] toArray(T[] a) wo Clients gezwungen werden, den erforderlichen Typ zu übergeben.
Aber auf der anderen Seite funktioniert es für public E get(int index) denn wenn Sie sich die Implementierung der Methode ansehen, würden Sie feststellen, dass, obwohl die Methode dasselbe Array von Object verwendet, um das Element am angegebenen Index zurückzugeben, es in ein Casting umgewandelt wird E
E elementData(int index) {
return (E) elementData[index];
}
Es ist der Java-Compiler, der zur Kompilierzeit ersetzt wird E mit Object
Gemeinschaft
Das allererste, was Sie verstehen müssen, ist was ArrayList own ist nur ein Array von Object
transient Object[] elementData;
Wenn es um den Grund geht T[] ist fehlgeschlagen, weil Sie ohne a kein Array mit generischem Typ erhalten können Class<T> und das liegt daran, dass der Typ von Java gelöscht wird (es gibt eine weitere Erklärung und wie man eine erstellt). Und die array[] auf dem Heap kennt seinen Typ dynamisch und Sie können ihn nicht umwandeln int[] zu String[]. Aus demselben Grund können Sie nicht wirken Object[] zu T[].
int[] ints = new int[3];
String[] strings = (String[]) ints;//java: incompatible types: int[] cannot be converted to java.lang.String[]
public <T> T[] a() {
Object[] objects = new Object[3];
return (T[])objects;
}
//ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
Integer[] a = new LearnArray().<Integer>a();
Aber was Sie in die stecken array ist nur ein Objekt, dessen Typ ist E(was vom Compiler überprüft wird), sodass Sie es einfach umwandeln können E was sicher und richtig ist.
return (E) elementData[index];
Kurz gesagt, Sie können nicht bekommen, was Sie nicht durch Besetzung haben. Du hast gerade Object[]Also toArray() kann einfach zurück Object[](Andernfalls muss man ihm eine geben Class<T> um ein neues Array mit diesem Typ zu erstellen). Du legst E in ArrayList<E>können Sie eine bekommen E mit get().
Gerald Mücke
Ein Array ist von einem anderen Typ als der Typ des Arrays. Es ist eine Art StringArray-Klasse anstelle der String-Klasse.
Vorausgesetzt, es wäre eine generische Methode möglich toArray() würde aussehen wie
private <T> T[] toArray() {
T[] result = new T[length];
//populate
return result;
}
Jetzt wird während der Kompilierung der Typ T gelöscht. Wie soll das Teil new T[length] ausgetauscht werden? Die generischen Typinformationen sind nicht verfügbar.
Wenn Sie sich den Quellcode von (zum Beispiel) ArrayList, siehst du genauso. Das toArray(T[] a) -Methode füllt entweder das angegebene Array (wenn die Größe übereinstimmt) oder erstellt ein neues neues Array mit der Typ des Parameters, der der Array-Typ des generischen Typs T ist.
Wie funktioniert die get(x) Methode, um dies zu umgehen? Es weiß, was es zurückgibt
– Kaninchentyp
13. April 2016 um 13:03 Uhr
Tut es nicht, zur Laufzeit gibt get(x) Object zurück. Dies wird Ihnen nur dadurch verborgen, dass der Compiler implizit Umwandlungen einfügt.
– Plugwash
13. April 2016 um 18:39 Uhr
10992000cookie-checkJava-Generika in ArrayList.toArray()yes
Worüber redest du? Welche Klasse hat die
toArray()
Methode?– Fisch
13. April 2016 um 12:52 Uhr
ArrayList hat eine
toArray()
Methode, aber selbst wenn Sie die generische, die definierentoArray()
Methode wird zurückgegebenObject[]
nichtE[]
was mit dem Generikum impliziert wird.– Kaninchentyp
13. April 2016 um 12:53 Uhr
Anstatt zu überschreiben
toArray()
Sie können verwendentoArray(T[] a)
Methode, um ein geeignetes Array zu erhalten.– nur ein wenig
13. April 2016 um 12:54 Uhr
Ja, ich weiß, ich kann
toArray(T[] a)
aber warum wurde es nicht einfach direkt eingebauttoArray()
. Ich verstehe nicht warumget(x)
weiß, was es ausgibt, abertoArray()
nicht.– Kaninchentyp
13. April 2016 um 12:56 Uhr
@JimmyB: Eine Alternative ist eine Art Fabrik für den dynamischen Typ, z Java 8
Stream.toArray
VerwendetIntFunction<ArrayType>
als Fabriktyp, als Parameter übergeben. Der Grund, warum dieses Muster bisher nicht verwendet wurde, zB für dieCollection
Schnittstelle, ist, dass nur Java 8 es erlaubt, es so ordentlich zu implementierenElementType[]::new
ermöglicht zString[] array = stream.toArray(String[]::new)
…– Holger
13. April 2016 um 15:45 Uhr