CodeGym /Java-Blog /Random-DE /Verwendung von Varargs bei der Arbeit mit Generika
Autor
John Selawsky
Senior Java Developer and Tutor at LearningTree

Verwendung von Varargs bei der Arbeit mit Generika

Veröffentlicht in der Gruppe Random-DE
Hallo! In der heutigen Lektion beschäftigen wir uns weiterhin mit Generika. Tatsächlich ist dies ein großes Thema, an dem man aber nicht vorbeikommt – es ist ein äußerst wichtiger Teil der Sprache :) Wenn Sie die Oracle-Dokumentation zu Generika studieren oder Online-Tutorials lesen, werden Sie auf die Begriffe „non-refiabletypes“ und „non-refiabletypes “ stoßen prüfbare Typen . Ein reifizierbarer Typ ist ein Typ, für den zur Laufzeit Informationen vollständig verfügbar sind. In Java umfassen solche Typen Primitive, Rohtypen und nicht generische Typen. Im Gegensatz dazu sind nicht verifizierbare Typen Typen, deren Informationen gelöscht werden und zur Laufzeit nicht mehr zugänglich sind. Zufälligerweise handelt es sich dabei um Generika – List<String>, List<Integer>usw.

Erinnern Sie sich übrigens, was Varargs ist?

Falls Sie es vergessen haben: Dies ist ein Argument variabler Länge. Sie sind in Situationen nützlich, in denen wir nicht wissen, wie viele Argumente an unsere Methode übergeben werden könnten. Zum Beispiel, wenn wir eine Taschenrechnerklasse haben, die über eine sumMethode verfügt. Die sum()Methode kann 2 Zahlen oder 3 oder 5 oder so viele empfangen, wie Sie möchten. Es wäre sehr seltsam, die sum()Methode für jede mögliche Anzahl von Argumenten zu überladen. Stattdessen können wir Folgendes tun:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Konsolenausgabe:

15
11
Dies zeigt uns, dass es einige wichtige Funktionen gibt, wenn Varargs in Kombination mit Generika verwendet werden. Schauen wir uns den folgenden Code an:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Die addAll()Methode verwendet als Eingabe ein List<E>und eine beliebige Anzahl von EObjekten und fügt dann alle diese Objekte zur Liste hinzu. In der main()Methode rufen wir unsere addAll()Methode zweimal auf. Im ersten Fall fügen wir zwei gewöhnliche Zeichenfolgen zur hinzu List. Hier ist alles in Ordnung. Im zweiten Fall fügen wir zwei Pair<String, String>Objekte zur hinzu List. Doch hier erhalten wir unerwartet eine Warnung:

Unchecked generics array creation for varargs parameter
Was bedeutet das? Warum erhalten wir eine Warnung und warum wird ein ? erwähnt array? Schließlich hat unser Code kein array! Beginnen wir mit dem zweiten Fall. In der Warnung wird ein Array erwähnt, da der Compiler das Argument variabler Länge (varargs) in ein Array konvertiert. Mit anderen Worten, die Signatur unserer addAll()Methode lautet:

public static <E> void addAll(List<E> list, E... array)
Es sieht tatsächlich so aus:

public static <E> void addAll(List<E> list, E[] array)
Das heißt, in der main()Methode wandelt der Compiler unseren Code wie folgt um:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
Ein StringArray ist völlig in Ordnung. Aber ein Pair<String, String>Array ist es nicht. Das Problem besteht darin, dass es Pair<String, String>sich um einen nicht verifizierbaren Typ handelt. Während der Kompilierung werden alle Informationen zu Typargumenten (<String, String>) gelöscht. Das Erstellen von Arrays eines nicht verifizierbaren Typs ist in Java nicht zulässig . Sie können dies sehen, wenn Sie versuchen, manuell ein Pair<String, String>-Array zu erstellen

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Der Grund liegt auf der Hand: Typensicherheit. Wie Sie sich erinnern, müssen Sie beim Erstellen eines Arrays unbedingt angeben, welche Objekte (oder Grundelemente) das Array speichern soll.

int array[] = new int[10];
In einer unserer vorherigen Lektionen haben wir uns ausführlich mit der Löschung von Schriftarten befasst. In diesem Fall führt die Typlöschung dazu, dass wir die Informationen verlieren, die die PairObjekte <String, String>paarweise speichern. Das Erstellen des Arrays wäre unsicher. Denken Sie bei der Verwendung von Methoden, die Varargs und Generics beinhalten , unbedingt an die Typlöschung und deren Funktionsweise. Wenn Sie sich über den von Ihnen geschriebenen Code absolut sicher sind und wissen, dass er keine Probleme verursachen wird, können Sie die Varargs-bezogenen Warnungen mithilfe der Anmerkungen deaktivieren . @SafeVarargs

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Wenn Sie diese Anmerkung zu Ihrer Methode hinzufügen, wird die zuvor aufgetretene Warnung nicht angezeigt. Ein weiteres Problem, das bei der Verwendung von Varargs mit Generika auftreten kann, ist die Heap-Verschmutzung. Verwendung von Varargs bei der Arbeit mit Generika – 3Eine Haufenverschmutzung kann in der folgenden Situation auftreten:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Konsolenausgabe:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Vereinfacht ausgedrückt liegt Heap-Verschmutzung vor, wenn sich Objekte dieses Typs Aim Heap befinden sollten, Objekte dieses Typs jedoch Baufgrund von Fehlern im Zusammenhang mit der Typsicherheit dort landen. In unserem Beispiel passiert genau das. Zuerst haben wir die Rohvariable erstellt und ihr numberseine generische Sammlung () zugewiesen . ArrayList<Number>Dann haben wir die Nummer 1zur Sammlung hinzugefügt.

List<String> strings = numbers;
In dieser Zeile versuchte der Compiler, uns vor möglichen Fehlern zu warnen, indem er die Warnung „ Ungeprüfte Zuweisung... “ ausgab , aber wir ignorierten sie. Am Ende erhalten wir eine generische Variable vom Typ List<String>, die auf eine generische Sammlung vom Typ verweist ArrayList<Number>. Offensichtlich kann diese Situation zu Problemen führen! Und so ist es auch. Mithilfe unserer neuen Variablen fügen wir der Sammlung einen String hinzu. Wir haben jetzt Heap-Verschmutzung – wir haben der parametrisierten Sammlung eine Zahl und dann eine Zeichenfolge hinzugefügt. Der Compiler hat uns gewarnt, aber wir haben seine Warnung ignoriert. Als Ergebnis erhalten wir ClassCastExceptionnur dann eine Fehlermeldung, wenn das Programm läuft. Was hat das also mit Varargs zu tun? Die Verwendung von Varargs mit Generika kann leicht zu einer Haufenverschmutzung führen. Hier ist ein einfaches Beispiel:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
Was ist denn hier los? Aufgrund der Typlöschung ist unser Argument variabler Länge

List<String>...stringsLists
wird zu einem Array von Listen, also List[]von Objekten eines unbekannten Typs (vergessen Sie nicht, dass sich varargs während der Kompilierung in ein reguläres Array verwandelt). Aus diesem Grund können wir es einfach der Object[] arrayVariablen in der ersten Zeile der Methode zuweisen – der Typ der Objekte in unseren Listen wurde gelöscht! Und jetzt haben wir eine Object[]Variable, zu der wir alles hinzufügen können, da alle Objekte in Java erben Object! Zunächst haben wir nur ein Array mit Listen von Zeichenfolgen. Aber dank der Typlöschung und unserer Verwendung von Varargs können wir ganz einfach eine Liste mit Zahlen hinzufügen, was wir auch tun. Infolgedessen verschmutzen wir den Haufen, indem wir Objekte unterschiedlichen Typs mischen. Das Ergebnis wird noch einmal anders sein, ClassCastExceptionwenn wir versuchen, eine Zeichenfolge aus dem Array zu lesen. Konsolenausgabe:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Solche unerwarteten Konsequenzen können durch die Verwendung von Varargs verursacht werden, einem scheinbar einfachen Mechanismus :) Und damit ist die heutige Lektion zu Ende. Vergessen Sie nicht, ein paar Aufgaben zu lösen, und lesen Sie, wenn Sie Zeit und Energie haben, zusätzliche Lektüre. „ Effektives Java “ liest sich nicht selbst! :) Bis zum nächsten Mal!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION