Java-Generika in ArrayList.toArray()

Lesezeit: 9 Minuten

Benutzer-Avatar
Kaninchen-Typ

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


Benutzer-Avatar
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).

Instanzen von Typparametern können nicht erstellt werden von Java Doc könnte auch für Sie interessant sein.

  • 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


Benutzer-Avatar
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.

    – Robert

    13. April 2016 um 12:58 Uhr

Schaut man sich die an Javadoc für die List Schnittstellewerden Sie eine zweite Form von bemerken toArray: <T> T[] toArray(T[] a).

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.

Benutzer-Avatar
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

Benutzer-Avatar
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().

Benutzer-Avatar
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

1099200cookie-checkJava-Generika in ArrayList.toArray()

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy