Erstellen eines generischen Arrays in Java

1. Einleitung

Möglicherweise möchten wir Arrays als Teil von Klassen oder Funktionen verwenden, die Generika unterstützen. Aufgrund der Art und Weise, wie Java mit Generika umgeht, kann dies schwierig sein.

In diesem Tutorial werden wir die Herausforderungen bei der Verwendung von Generika mit Arrays verstehen. Anschließend erstellen wir ein Beispiel für ein generisches Array.

Wir werden uns auch ansehen, wo die Java-API ein ähnliches Problem gelöst hat.

2. Überlegungen zur Verwendung generischer Arrays

Ein wichtiger Unterschied zwischen Arrays und Generika besteht darin, wie sie die Typprüfung erzwingen. Insbesondere speichern und überprüfen Arrays zur Laufzeit Typinformationen. Generics suchen jedoch zur Kompilierungszeit nach Typfehlern und haben zur Laufzeit keine Typinformationen.

Die Syntax von Java legt nahe, dass wir möglicherweise ein neues generisches Array erstellen können:

T[] elements = new T[size];

Wenn wir dies versuchen würden, würden wir einen Kompilierungsfehler erhalten.

Um zu verstehen, warum, betrachten wir Folgendes:

public  T[] getArray(int size) { T[] genericArray = new T[size]; // suppose this is allowed return genericArray; }

Da ein ungebundener generischer Typ T in Object aufgelöst wird, lautet unsere Methode zur Laufzeit:

public Object[] getArray(int size) { Object[] genericArray = new Object[size]; return genericArray; }

Wenn wir dann unsere Methode aufrufen und das Ergebnis in einem String- Array speichern :

String[] myArray = getArray(5);

Der Code wird einwandfrei kompiliert, schlägt jedoch zur Laufzeit mit einer ClassCastException fehl . Dies liegt daran, dass wir einer String [] -Referenz gerade ein Objekt [] zugewiesen haben . Insbesondere würde eine implizite Umwandlung durch den Compiler Object [] nicht in unseren erforderlichen Typ String [] konvertieren .

Obwohl wir generische Arrays nicht direkt initialisieren können, ist es dennoch möglich, die entsprechende Operation zu erzielen, wenn der aufrufende Code den genauen Informationstyp bereitstellt.

3. Erstellen eines generischen Arrays

In unserem Beispiel betrachten wir eine begrenzte Stack-Datenstruktur MyStack , bei der die Kapazität auf eine bestimmte Größe festgelegt ist. Da wir möchten, dass der Stack mit jedem Typ funktioniert, wäre eine vernünftige Implementierungsoption ein generisches Array.

Erstellen wir zunächst ein Feld zum Speichern der Elemente unseres Stapels, bei dem es sich um ein generisches Array vom Typ E handelt :

private E[] elements;

Zweitens fügen wir einen Konstruktor hinzu:

public MyStack(Class clazz, int capacity) { elements = (E[]) Array.newInstance(clazz, capacity); }

Beachten Sie, wie wir java.lang.reflect.Array # newInstance verwenden , um unser generisches Array zu initialisieren , für das zwei Parameter erforderlich sind. Der erste Parameter gibt den Objekttyp innerhalb des neuen Arrays an. Der zweite Parameter gibt an, wie viel Speicherplatz für das Array erstellt werden soll. Da das Ergebnis von Array # newInstance vom Typ Object ist , müssen wir es in E [] umwandeln , um unser generisches Array zu erstellen.

Wir sollten auch die Konvention beachten, einen Typparameter clazz anstelle von class zu benennen , was in Java ein reserviertes Wort ist.

4. Berücksichtigung von ArrayList

4.1. Verwenden von ArrayList anstelle eines Arrays

Es ist oft einfacher, eine generische ArrayList anstelle eines generischen Arrays zu verwenden. Mal sehen, wie wir MyStack so ändern können , dass eine ArrayList verwendet wird .

Erstellen wir zunächst ein Feld zum Speichern unserer Elemente:

private List elements;

Zweitens können wir in unserem Stapelkonstruktor die ArrayList mit einer Anfangskapazität initialisieren :

elements = new ArrayList(capacity);

Dies vereinfacht unseren Unterricht, da wir keine Reflexion verwenden müssen. Außerdem müssen wir beim Erstellen unseres Stapels kein Klassenliteral übergeben. Da wir schließlich die Anfangskapazität einer ArrayList festlegen können , können wir dieselben Vorteile wie bei einem Array erzielen.

Daher müssen wir Arrays von Generika nur in seltenen Situationen erstellen oder wenn wir eine Schnittstelle zu einer externen Bibliothek herstellen, für die ein Array erforderlich ist.

4.2. ArrayList- Implementierung

Interessanterweise wird ArrayList selbst mithilfe generischer Arrays implementiert. Werfen wir einen Blick in ArrayList, um zu sehen, wie.

Schauen wir uns zunächst das Listenelementfeld an:

transient Object[] elementData;

Hinweis ArrayList verwendet Object als Elementtyp. Da unser generischer Typ erst zur Laufzeit bekannt ist, wird Object als Oberklasse eines beliebigen Typs verwendet.

Es ist erwähnenswert, dass fast alle Operationen in ArrayList dieses generische Array verwenden können, da sie der Außenwelt kein stark typisiertes Array bereitstellen müssen, mit Ausnahme einer Methode - toArray !

5. Erstellen eines Arrays aus einer Sammlung

5.1. LinkedList Beispiel

Schauen wir uns die Verwendung generischer Arrays in der Java Collections-API an, in der wir aus einer Sammlung ein neues Array erstellen.

Erstellen wir zunächst eine neue LinkedList mit einem Typargument String und fügen Elemente hinzu:

List items = new LinkedList(); items.add("first item"); items.add("second item"); 

Zweitens erstellen wir ein Array der Elemente, die wir gerade hinzugefügt haben:

String[] itemsAsArray = items.toArray(new String[0]);

Um unser Array zu erstellen, die Liste . Die toArray- Methode erfordert ein Eingabearray. Es verwendet dieses Array lediglich, um die Typinformationen abzurufen und ein Rückgabearray des richtigen Typs zu erstellen.

In unserem obigen Beispiel haben wir den neuen String [0] als Eingabearray verwendet, um das resultierende String- Array zu erstellen .

5.2. LinkedList.toArray- Implementierung

Werfen wir einen Blick in LinkedList.toArray , um zu sehen, wie es im Java JDK implementiert ist.

Schauen wir uns zunächst die Methodensignatur an:

public  T[] toArray(T[] a)

Zweitens wollen wir sehen, wie bei Bedarf ein neues Array erstellt wird:

a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

Beachten Sie, wie Array # newInstance zum Erstellen eines neuen Arrays verwendet wird, wie in unserem vorherigen Stapelbeispiel . Beachten Sie auch, wie Parameter a verwendet wird, um Array # newInstance einen Typ bereitzustellen. Schließlich wird das Ergebnis von Array # newInstance in T [] umgewandelt, um ein generisches Array zu erstellen.

6. Fazit

In diesem Artikel haben wir uns zunächst mit den Unterschieden zwischen Arrays und Generika befasst, gefolgt von einem Beispiel für die Erstellung eines generischen Arrays. Dann haben wir gezeigt, wie die Verwendung einer ArrayList einfacher sein kann als die Verwendung eines generischen Arrays. Schließlich haben wir uns auch mit der Verwendung eines generischen Arrays in der Collections-API befasst.

Wie immer ist der Beispielcode auf GitHub verfügbar.