CodeGym /Java-Blog /Random-DE /Generika in Java
Autor
Volodymyr Portianko
Java Engineer at Playtika

Generika in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! Wir werden über Java Generics sprechen. Ich muss sagen, dass Sie viel lernen werden! Nicht nur diese Lektion, sondern auch die nächsten Lektionen werden den Generika gewidmet sein. Wenn Sie sich also für Generika interessieren, ist heute Ihr Glückstag: Sie erfahren viel über die Eigenschaften von Generika. Und wenn nicht, resignieren Sie und entspannen Sie sich! :) Das ist ein sehr wichtiges Thema, und Sie müssen es wissen. Beginnen wir mit dem Einfachen: dem „Was“ und dem „Warum“.

Was sind Java-Generika?

Generics sind Typen, die einen Parameter haben. Beim Erstellen eines generischen Typs geben Sie nicht nur einen Typ an, sondern auch den Datentyp, mit dem er arbeiten soll. Ich vermute, das offensichtlichste Beispiel ist Ihnen bereits in den Sinn gekommen: ArrayList! So erstellen wir normalerweise eines in einem Programm:

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

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Wie Sie vielleicht erraten haben, besteht ein Merkmal dieser Liste darin, dass wir nicht alles hineinstopfen können: Sie funktioniert ausschließlich mit String- Objekten. Lassen Sie uns nun einen kleinen Exkurs in die Geschichte von Java machen und versuchen, die Frage „Warum?“ zu beantworten. Dazu schreiben wir unsere eigene vereinfachte Version der ArrayList-Klasse. Unsere Liste kennt nur das Hinzufügen von Daten zu einem internen Array und das Abrufen von Daten aus diesem:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Angenommen, wir möchten, dass unsere Liste nur Integer -Werte speichert . Wir verwenden keinen generischen Typ. Wir möchten keine explizite „instanceof Integer “-Prüfung in die add()- Methode einbinden. Wenn wir das täten, wäre unsere gesamte Klasse nur für Integer geeignet und wir müssten für jeden anderen Datentyp auf der Welt eine ähnliche Klasse schreiben! Wir verlassen uns auf unsere Programmierer und hinterlassen einfach einen Kommentar im Code, um sicherzustellen, dass sie nichts hinzufügen, was wir nicht wollen:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Einer der Programmierer hat diesen Kommentar übersehen und versehentlich mehrere Strings in eine Zahlenliste eingefügt und dann deren Summe berechnet:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Konsolenausgabe:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Was ist das Schlimmste an dieser Situation? Sicherlich nicht die Nachlässigkeit des Programmierers. Das Schlimmste ist, dass falscher Code an einer wichtigen Stelle in unserem Programm landete und erfolgreich kompiliert wurde. Jetzt wird der Fehler nicht beim Schreiben von Code auftreten, sondern nur beim Testen (und das ist das beste Szenario!). Das Beheben von Fehlern in späteren Entwicklungsstadien kostet viel mehr – sowohl in Bezug auf Geld als auch auf Zeit. Genau hier liegt der Nutzen von Generika: Eine generische Klasse ermöglicht es dem unglücklichen Programmierer, den Fehler sofort zu erkennen. Das Programm lässt sich einfach nicht kompilieren!

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

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Der Programmierer erkennt seinen Fehler sofort und wird sofort besser. Übrigens mussten wir keine eigene List- Klasse erstellen, um diese Art von Fehler zu sehen. Entfernen Sie einfach die spitzen Klammern und geben Sie ( <Integer> ) aus einer gewöhnlichen ArrayList ein!

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

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Konsolenausgabe:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
Mit anderen Worten: Selbst wenn wir die „nativen“ Mechanismen von Java verwenden, können wir einen solchen Fehler machen und eine unsichere Sammlung erstellen. Wenn wir diesen Code jedoch in eine IDE einfügen, erhalten wir eine Warnung: „Unchecked call to add(E) as a member of raw type of java.util.List“ Uns wird gesagt, dass beim Hinzufügen eines Elements etwas schief gehen könnte zu einer Sammlung, der ein generischer Typ fehlt. Aber was bedeutet der Ausdruck „Rohtyp“? Ein Rohtyp ist eine generische Klasse, deren Typ entfernt wurde. Mit anderen Worten: List myList1 ist ein Rohtyp . Das Gegenteil eines Rohtyps ist ein generischer Typ – eine generische Klasse mit einer Angabe des/der parametrisierten Typ(s) . Beispiel: List<String> myList1. Sie fragen sich vielleicht, warum die Sprache die Verwendung von Rohtypen zulässt ? Der Grund ist einfach. Die Entwickler von Java haben die Unterstützung für Rohtypen in der Sprache beibehalten, um Kompatibilitätsprobleme zu vermeiden. Als Java 5.0 veröffentlicht wurde (Generika erschienen erstmals in dieser Version), war bereits viel Code mit Rohtypen geschrieben worden . Daher wird dieser Mechanismus auch heute noch unterstützt. Wir haben in den Lektionen wiederholt Joshua Blochs klassisches Buch „Effective Java“ erwähnt. Als einer der Schöpfer der Sprache hat er in seinem Buch Rohtypen und generische Typen nicht ausgelassen.Was sind Generika in Java?  - 2Kapitel 23 des Buches hat einen sehr treffenden Titel: „Verwenden Sie keine Rohtypen in neuem Code.“ Das müssen Sie sich merken. Wenn Sie generische Klassen verwenden, wandeln Sie niemals einen generischen Typ in einen Rohtyp um .

Generische Methoden

Mit Java können Sie einzelne Methoden parametrisieren, indem Sie sogenannte generische Methoden erstellen. Wie hilfreich sind solche Methoden? Sie sind vor allem insofern hilfreich, als sie das Arbeiten mit unterschiedlichen Arten von Methodenparametern ermöglichen. Wenn dieselbe Logik sicher auf verschiedene Typen angewendet werden kann, kann eine generische Methode eine gute Lösung sein. Betrachten Sie dies als ein sehr einfaches Beispiel: Angenommen, wir haben eine Liste namens myList1 . Wir möchten alle Werte aus der Liste entfernen und alle Leerstellen mit neuen Werten füllen. So sieht unsere Klasse mit einer generischen Methode aus:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Achten Sie auf die Syntax. Es sieht etwas ungewöhnlich aus:

public static <T> void fill(List<T> list, T val)
Wir schreiben <T> vor den Rückgabetyp. Dies weist darauf hin, dass es sich um eine generische Methode handelt. In diesem Fall akzeptiert die Methode zwei Parameter als Eingabe: eine Liste von T-Objekten und ein weiteres separates T-Objekt. Mithilfe von <T> parametrisieren wir die Parametertypen der Methode: Wir können keine Liste von Strings und eine Ganzzahl übergeben. Eine Liste mit Strings und einem String, eine Liste mit Integers und einer Integer, eine Liste unserer eigenen Cat- Objekte und ein weiteres Cat- Objekt – das ist es, was wir tun müssen. Die main()- Methode veranschaulicht, wie die fill()- Methode einfach zum Arbeiten mit verschiedenen Datentypen verwendet werden kann. Zuerst verwenden wir die Methode mit einer Liste von Strings und einem String als Eingabe und dann mit einer Liste von Integers und einer Integer. Konsolenausgabe:

[New String, New String, New String] [888, 888, 888]
Stellen Sie sich vor, wir hätten keine generischen Methoden und bräuchten die Logik der fill()- Methode für 30 verschiedene Klassen. Wir müssten die gleiche Methode 30 Mal für verschiedene Datentypen schreiben! Aber dank generischer Methoden können wir unseren Code wiederverwenden! :) :)

Generische Klassen

Sie sind nicht auf die generischen Klassen beschränkt, die in den Standard-Java-Bibliotheken bereitgestellt werden – Sie können Ihre eigenen erstellen! Hier ist ein einfaches Beispiel:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Unsere Box<T> -Klasse ist eine generische Klasse. Sobald wir bei der Erstellung einen Datentyp ( <T> ) zuweisen, können wir darin keine Objekte anderen Typs mehr platzieren. Dies ist im Beispiel zu sehen. Beim Erstellen unseres Objekts haben wir angegeben, dass es mit Strings funktionieren würde:

Box<String> stringBox = new Box<>();
Und wenn wir in der letzten Codezeile versuchen, die Zahl 12345 in das Feld einzufügen, erhalten wir einen Kompilierungsfehler! So einfach ist das! Wir haben unsere eigene generische Klasse erstellt! :) Damit ist die heutige Lektion zu Ende. Aber wir verabschieden uns nicht von Generika! In den nächsten Lektionen werden wir über erweiterte Funktionen sprechen, also lassen Sie sich nicht entmutigen! ) Um das Gelernte zu vertiefen, empfehlen wir Ihnen, sich eine Videolektion aus unserem Java-Kurs anzusehen
Viel Erfolg im Studium! :) :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION