JavaRush /Java-Blog /Random-DE /Kaffeepause Nr. 130. So arbeiten Sie richtig mit Java-Arr...

Kaffeepause Nr. 130. So arbeiten Sie richtig mit Java-Arrays – Tipps von Oracle

Veröffentlicht in der Gruppe Random-DE
Quelle: Oracle Die Arbeit mit Arrays kann Reflektion, Generika und Lambda-Ausdrücke umfassen. Ich habe kürzlich mit einem Kollegen gesprochen, der in C entwickelt. Das Gespräch drehte sich um Arrays und wie sie in Java im Vergleich zu C funktionieren. Ich fand das etwas seltsam, wenn man bedenkt, dass Java als eine C-ähnliche Sprache gilt. Sie haben tatsächlich viele Gemeinsamkeiten, es gibt aber auch Unterschiede. Fangen wir einfach an. Kaffeepause Nr. 130.  So arbeiten Sie richtig mit Java-Arrays - Tipps von Oracle - 1

Array-Deklaration

Wenn Sie dem Java-Tutorial folgen, werden Sie feststellen, dass es zwei Möglichkeiten gibt, ein Array zu deklarieren. Der erste ist einfach:
int[] array; // a Java array declaration
Sie können sehen, wie es sich von C unterscheidet, wo die Syntax wie folgt lautet:
int array[]; // a C array declaration
Kehren wir noch einmal zu Java zurück. Nachdem Sie ein Array deklariert haben, müssen Sie es zuweisen:
array = new int[10]; // Java array allocation
Ist es möglich, ein Array gleichzeitig zu deklarieren und zu initialisieren? Nicht wirklich:
int[10] array; // NOPE, ERROR!
Sie können das Array jedoch auch sofort deklarieren und initialisieren, wenn Sie die Werte bereits kennen:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
Was ist, wenn Sie die Bedeutung nicht kennen? Hier ist der Code, den Sie am häufigsten zum Deklarieren, Zuweisen und Verwenden eines int- Arrays sehen :
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
Beachten Sie, dass ich ein int- Array angegeben habe , bei dem es sich um ein Array primitiver Java-Datentypen handelt . Sehen wir uns an, was passiert, wenn Sie den gleichen Prozess mit einem Array von Java-Objekten anstelle von Grundelementen versuchen:
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Wenn wir den obigen Code ausführen, erhalten wir sofort eine Ausnahme, nachdem wir versucht haben, das erste Element des Arrays zu verwenden. Warum? Auch wenn das Array zugeordnet ist, enthält jedes Segment des Arrays leere Objektverweise. Wenn Sie diesen Code in Ihre IDE eingeben, wird die .val-Datei sogar automatisch für Sie ausgefüllt, sodass der Fehler möglicherweise verwirrend ist. Um den Fehler zu beheben, gehen Sie folgendermaßen vor:
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
Aber es ist nicht elegant. Ich habe mich gefragt, warum ich ein Array und die darin enthaltenen Objekte nicht einfach mit weniger Code zuordnen konnte, vielleicht sogar alle in einer Zeile. Um die Antwort zu finden, habe ich mehrere Experimente durchgeführt.

Das Nirwana unter Java-Arrays finden

Unser Ziel ist es, elegant zu programmieren. Den Regeln des „sauberen Codes“ folgend, habe ich beschlossen, wiederverwendbaren Code zu erstellen, um das Array-Zuordnungsmuster zu bereinigen. Hier der erste Versuch:
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
Die mit „see this“ gekennzeichnete Codezeile sieht dank der toArray- Implementierung genau so aus, wie ich es wollte . Dieser Ansatz verwendet Reflektion, um den Standardkonstruktor für die bereitgestellte Klasse zu finden, und ruft dann diesen Konstruktor auf, um ein Objekt dieser Klasse zu instanziieren. Der Prozess ruft den Konstruktor einmal für jedes Array-Element auf. Fabelhaft! Schade nur, dass es nicht funktioniert. Der Code lässt sich gut kompilieren, löst jedoch beim Ausführen einen ClassCastException- Fehler aus . Um diesen Code zu verwenden, müssen Sie ein Array von Object- Elementen erstellen und dann jedes Element des Arrays wie folgt in eine SomeClass- Klasse umwandeln:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
Das ist nicht elegant! Nach weiteren Experimenten entwickelte ich mehrere Lösungen unter Verwendung von Reflektion, Generika und Lambda-Ausdrücken.

Lösung 1: Nutzen Sie Reflexion

Hier verwenden wir die Klasse java.lang.reflect.Array , um ein Array der von Ihnen angegebenen Klasse zu instanziieren, anstatt die Basisklasse java.lang.Object zu verwenden . Dies ist im Wesentlichen eine einzeilige Codeänderung:
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
Mit diesem Ansatz können Sie ein Array der gewünschten Klasse abrufen und dann wie folgt damit arbeiten:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
Obwohl dies keine erforderliche Änderung ist, wurde die zweite Zeile geändert, um die Array-Reflektionsklasse zum Festlegen des Inhalts jedes Array-Elements zu verwenden. Das ist großartig! Aber es gibt noch ein weiteres Detail, das nicht ganz richtig zu sein scheint: Die Umwandlung in SomeClass[] sieht nicht sehr gut aus. Glücklicherweise gibt es eine Lösung mit Generika.

Lösung 2: Generika verwenden

Das Collections- Framework verwendet Generika für die Typbindung und eliminiert bei vielen seiner Vorgänge die Umwandlung in diese. Auch hier können Generika zum Einsatz kommen. Nehmen wir zum Beispiel java.util.List .
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
Die dritte Zeile im obigen Snippet gibt einen Fehler aus, es sei denn, Sie aktualisieren die erste Zeile wie folgt:
List<SomeClass> = new ArrayList();
Sie können das gleiche Ergebnis erzielen, indem Sie Generics in der MyArray- Klasse verwenden . Hier ist die neue Version:
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
Es sieht gut aus. Durch die Verwendung von Generika und die Aufnahme des Zieltyps in die Deklaration kann der Typ in anderen Vorgängen abgeleitet werden . Darüber hinaus kann dieser Code folgendermaßen auf eine Zeile reduziert werden:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
Mission erfüllt, oder? Nicht ganz. Dies ist in Ordnung, wenn es Ihnen egal ist, welchen Klassenkonstruktor Sie aufrufen. Wenn Sie jedoch einen bestimmten Konstruktor aufrufen möchten, funktioniert diese Lösung nicht. Sie können die Reflektion weiterhin verwenden, um dieses Problem zu lösen, aber dann wird der Code komplex. Glücklicherweise gibt es Lambda-Ausdrücke, die eine andere Lösung bieten.

Lösung 3: Verwenden Sie Lambda-Ausdrücke

Ich gebe zu, ich war vorher nicht besonders begeistert von Lambda-Ausdrücken, aber ich habe gelernt, sie zu schätzen. Besonders gut hat mir die java.util.stream.Stream- Schnittstelle gefallen , die Sammlungen von Objekten verwaltet. Stream hat mir geholfen, das Java-Array-Nirvana zu erreichen. Hier ist mein erster Versuch, Lambdas zu verwenden:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
Ich habe diesen Code zur leichteren Lesbarkeit in drei Zeilen unterteilt. Sie sehen, dass es alle Anforderungen erfüllt: Es ist einfach und elegant, erstellt ein gefülltes Array von Objektinstanzen und ermöglicht den Aufruf eines bestimmten Konstruktors. Achten Sie auf den toArray- Methodenparameter : SomeClass[]::new . Dies ist eine Generatorfunktion, die zum Zuweisen eines Arrays des angegebenen Typs verwendet wird. Allerdings hat dieser Code in seiner jetzigen Form ein kleines Problem: Er erstellt ein Array von unendlicher Größe. Das ist nicht sehr optimal. Das Problem kann jedoch durch den Aufruf der limit- Methode gelöst werden :
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
Das Array ist jetzt auf 32 Elemente begrenzt. Sie können sogar für jedes Element des Arrays spezifische Objektwerte festlegen, wie unten gezeigt:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
Dieser Code demonstriert die Leistungsfähigkeit von Lambda-Ausdrücken, aber der Code ist weder übersichtlich noch kompakt. Meiner Meinung nach wäre es viel besser, einen anderen Konstruktor aufzurufen, um den Wert festzulegen.
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
Ich mag die auf Lambda-Ausdrücken basierende Lösung. Dies ist ideal, wenn Sie einen bestimmten Konstruktor aufrufen oder mit jedem Element eines Arrays arbeiten müssen. Wenn ich etwas Einfacheres brauche, verwende ich normalerweise eine generische Lösung, weil sie einfacher ist. Sie können jedoch selbst sehen, dass Lambda-Ausdrücke eine elegante und flexible Lösung darstellen.

Abschluss

Heute haben wir gelernt, wie man mit der Deklaration und Zuweisung von Arrays von Grundelementen, der Zuweisung von Arrays von Objektelementen sowie der Verwendung von Reflektion, Generika und Lambda-Ausdrücken in Java arbeitet.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION