Einführung in Generics
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy — Lektion 53 von 87.
Stellen Sie sich vor, Sie müssen eine Klasse erstellen, die einen Wert speichert und ihn später wieder abruft. Ohne Generics müssten Sie für jeden Typ separate Klassen schreiben – eine für Integer, eine für String, eine für Double und so weiter. Dies führt zu Codeduplizierung und Wartungsproblemen.
Generics lösen dieses Problem, indem sie es Ihnen ermöglichen, Klassen und Methoden zu schreiben, die mit jedem Typ funktionieren, während die Typsicherheit gewahrt bleibt. Sie definieren einen Platzhalter (einen sogenannten Typparameter), der durch einen tatsächlichen Typ ersetzt wird, wenn Sie die Klasse verwenden:
// Ohne Generics - verwendet Object, erfordert Casting
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manuelles Casting erforderlich
// Mit Generics - typsicher, kein Casting
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // Kein Casting erforderlichDie spitzen Klammern <T> bezeichnen einen Typparameter. Wenn Sie List<String> schreiben, teilen Sie dem Compiler mit, dass diese Liste nur Strings enthalten wird. Wenn Sie versuchen, einen Integer hinzuzufügen, erkennt der Compiler den Fehler sofort, anstatt erst zur Laufzeit zu scheitern.
Generics bieten zwei wesentliche Vorteile: Sie erübrigen explizites Casting und sie erkennen Typfehler bereits zur Kompilierzeit statt erst zur Laufzeit. Dies macht Ihren Code sowohl sicherer als auch sauberer. In den folgenden Lektionen werden wir untersuchen, wie Sie Ihre eigenen generischen Klassen und Methoden erstellen.
Aufgabe
EinfachLassen Sie uns ein einfaches Speichersystem erstellen, das die Leistungsfähigkeit von Generics demonstriert, indem wir den alten Weg (Verwendung von Object mit Casting) mit dem neuen Weg (Verwendung von Generics mit Typsicherheit) vergleichen.
Sie werden drei Dateien erstellen, um den Unterschied hautnah zu erleben:
OldBox.java: Erstellen Sie eine Klasse, die Werte auf die Art und Weise vor der Einführung von Generics speichert. Ihre OldBox sollte ein privates Feldcontentvom TypObjecthaben. Fügen Sie einen Konstruktor hinzu, der ein Object akzeptiert und speichert, sowie einegetContent()-Methode, die das Object zurückgibt. Dieser Ansatz funktioniert, erfordert aber ein Casting beim Abrufen von Werten.GenericBox.java: Erstellen Sie nun die moderne, typsichere Version unter Verwendung von Generics. Ihre GenericBox sollte einen TypparameterTin ihrer Deklaration verwenden. Sie benötigt ein privates Feldcontentvom TypT, einen Konstruktor, der einenT-Wert akzeptiert, und einegetContent()-Methode, die den TypTzurückgibt. Bei der Verwendung dieser Klasse ist kein Casting erforderlich!Main.java: Demonstrieren Sie beide Ansätze nebeneinander. Sie erhalten zwei Eingaben: ein Wort (String) und eine Nummer (Integer).Verwenden Sie zuerst den alten Ansatz: Erstellen Sie eine OldBox, die das Wort speichert, rufen Sie es dann mit einem Cast zu String ab und geben Sie aus:
OldBox (with cast): [value]Verwenden Sie als Nächstes den generischen Ansatz: Erstellen Sie eine
GenericBox<String>, die dasselbe Wort speichert, rufen Sie es ohne Casting ab und geben Sie aus:GenericBox (no cast): [value]Zeigen Sie dann, wie Generics mit Integern funktionieren: Erstellen Sie eine
GenericBox<Integer>, die die Nummer speichert, rufen Sie sie ab und geben Sie aus:GenericBox Integer: [value]Geben Sie schließlich eine Leerzeile aus, gefolgt von:
Type safety: Generics catch errors at compile time!
Sie erhalten zwei Eingaben nacheinander: ein Wort (String) und eine Nummer (Integer als String, den Sie parsen müssen).
Beachten Sie den entscheidenden Unterschied: Bei OldBox müssen Sie (String) box.getContent() casten und hoffen, dass Sie den richtigen Typ erwischt haben. Bei GenericBox kennt der Compiler den Typ und es ist kein Casting erforderlich. Dies ist der Hauptvorteil von Generics – Typsicherheit ohne den Aufwand des Castings!
Spickzettel
Generics ermöglichen es Ihnen, Klassen und Methoden zu schreiben, die mit jedem Typ funktionieren, während die Typsicherheit gewahrt bleibt. Sie verwenden Typparameter (Platzhalter), die durch tatsächliche Typen ersetzt werden, wenn die Klasse verwendet wird.
Ohne Generics (unter Verwendung von Object):
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manual casting requiredMit Generics (typsicher):
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // No casting neededErstellen einer generischen Klasse:
public class GenericBox<T> {
private T content;
public GenericBox(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}Verwenden einer generischen Klasse:
GenericBox<String> stringBox = new GenericBox<String>("Hello");
String value = stringBox.getContent(); // No casting
GenericBox<Integer> intBox = new GenericBox<Integer>(42);
Integer number = intBox.getContent(); // Type-safeDie spitzen Klammern <T> bezeichnen einen Typparameter. Zu den Vorteilen gehören: kein explizites Casting erforderlich und Typfehler werden zur Kompilierzeit statt zur Laufzeit abgefangen.
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String word = scanner.nextLine();
int number = Integer.parseInt(scanner.nextLine());
// TODO: Erstelle eine OldBox, die word speichert
// Mit Cast zu String abrufen und ausgeben: "OldBox (with cast): [value]"
// TODO: Erstelle eine GenericBox<String>, die word speichert
// Ohne Casting abrufen und ausgeben: "GenericBox (no cast): [value]"
// TODO: Erstelle eine GenericBox<Integer>, die number speichert
// Abrufen und ausgeben: "GenericBox Integer: [value]"
// TODO: Eine Leerzeile ausgeben, dann ausgeben:
// "Type safety: Generics catch errors at compile time!"
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas this-SchlüsselwortMethodenFelder (Attribute)Konstruktor-MethodeKonstruktor-ÜberladungRückblick – Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (extends)Das super-SchlüsselwortMethoden überschreiben (@Override)Konstruktor-VerkettungDie Object-KlasseEinfache & mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenZusammenfassung - Mitarbeiter-Hierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-InterfaceZusammenfassung – Benutzerdefiniertes Sortieren2Zugriffsmodifikatoren & Kapselung
Überblick über ZugriffsebenenGetter- und Setter-MethodenInformation HidingDas final-SchlüsselwortRückblick – Bankkonto-Manager5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenZusammenfassung – Formenrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, geschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Sealed Classes (Java 17+)11Design Patterns Teil 1
Einführung in Design PatternsSingleton-PatternFactory-PatternBuilder-PatternObserver-PatternStrategy-Pattern14Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung3Klasseneigenschaften & Statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Zusammenfassung – Zähler & Utility6Interfaces & Abstrakte Klassen
Einführung in InterfacesInterfaces implementierenImplementierung mehrerer InterfacesDefault- & statische Methoden in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesZusammenfassung - Zahlungssystem