Einführung in Generics
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 53 von 87.
Stell dir vor, du musst eine Klasse erstellen, die einen Wert speichert und ihn später abruft. Ohne Generics müsstest du für jeden Typ eine eigene Klasse schreiben: eine für Integer, eine für String, eine für Double und so weiter. Das führt zu doppeltem Code und macht die Wartung mühsam.
Generics lösen dieses Problem, indem sie dir ermöglichen, Klassen und Methoden zu schreiben, die mit jedem Typ funktionieren und dabei trotzdem die Typsicherheit gewährleisten. Du definierst einen Platzhalter (einen sogenannten Typparameter), der bei der Verwendung der Klasse durch einen tatsächlichen Typ ersetzt wird:
// Ohne Generics – verwendet Object, erfordert eine Typumwandlung
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // Manuelle Typumwandlung erforderlich
// Mit Generics – typsicher, keine Typumwandlung erforderlich
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0); // Keine Typumwandlung erforderlichDie spitzen Klammern <T> kennzeichnen einen Typparameter. Wenn du List<String> schreibst, teilst du dem Compiler mit, dass diese Liste nur Zeichenfolgen enthält. Wenn du versuchst, eine Ganzzahl hinzuzufügen, erkennt der Compiler den Fehler sofort, anstatt dass er erst zur Laufzeit auftritt.
Generics bieten zwei entscheidende Vorteile: Sie machen explizite Typumwandlungen überflüssig und erkennen Typfehler zur Compile-Zeit statt zur Laufzeit. Dadurch wird dein Code sowohl sicherer als auch übersichtlicher. In den folgenden Lektionen sehen wir uns an, wie du eigene generische Klassen und Methoden erstellst.
Aufgabe
EinfachErstellen wir ein einfaches Speichersystem, das die Leistungsfähigkeit von Generics veranschaulicht, indem es die alte Vorgehensweise (mit Object und Typumwandlung) mit der neuen Vorgehensweise (mit Generics und Typsicherheit) vergleicht.
Du erstellst drei Dateien, um den Unterschied direkt zu sehen:
OldBox.java: Erstelle eine Klasse, die Werte auf die Vorgehensweise vor Generics speichert. Deine OldBox sollte ein privates Feldcontentvom TypObjecthaben. Füge einen Konstruktor hinzu, der ein Object entgegennimmt und speichert, sowie eine MethodegetContent(), die das Object zurückgibt. Diese Vorgehensweise funktioniert, erfordert aber beim Abrufen von Werten eine Typumwandlung.GenericBox.java: Erstelle nun die moderne, typsichere Version mit Generics. Deine GenericBox sollte in ihrer Deklaration den TypparameterTverwenden. Sie benötigt ein privates Feldcontentvom TypT, einen Konstruktor, der einen Wert vom TypTentgegennimmt, und eine MethodegetContent(), die den TypTzurückgibt. Bei der Verwendung dieser Klasse ist keine Typumwandlung erforderlich!Main.java: Veranschauliche beide Vorgehensweisen nebeneinander. Du erhältst zwei Eingaben: ein Wort (String) und eine Zahl (integer).Verwende zuerst die alte Vorgehensweise: Erstelle eine OldBox, in der das Wort gespeichert wird, rufe es dann mit einer Typumwandlung zu String ab und gib Folgendes aus:
OldBox (with cast): [value]Verwende als Nächstes die generische Vorgehensweise: Erstelle eine
GenericBox<String>, in der dasselbe Wort gespeichert wird, rufe es ohne Typumwandlung ab und gib Folgendes aus:GenericBox (no cast): [value]Zeige dann, dass Generics auch mit Ganzzahlen funktionieren: Erstelle eine
GenericBox<Integer>, in der die Zahl gespeichert wird, rufe sie ab und gib Folgendes aus:GenericBox Integer: [value]Gib zum Schluss eine Leerzeile und danach Folgendes aus:
Type safety: Generics catch errors at compile time!
Du erhältst der Reihe nach zwei Eingaben: ein Wort (String) und eine Zahl (integer als String, die du parsen wirst).
Beachte den entscheidenden Unterschied: Bei OldBox musst du (String) box.getContent() verwenden und hoffen, dass der Typ stimmt. Bei GenericBox kennt der Compiler den Typ, sodass keine Typumwandlung erforderlich ist. Das ist der entscheidende Vorteil von Generics: Typsicherheit ohne den Aufwand für Typumwandlungen!
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 das Wort speichert
// Rufe den Wert mit einem Cast zu String ab und gib Folgendes aus: "OldBox (with cast): [value]"
// TODO: Erstelle eine GenericBox<String>, die das Wort speichert
// Rufe den Wert ohne Cast ab und gib Folgendes aus: "GenericBox (no cast): [value]"
// TODO: Erstelle eine GenericBox<Integer>, die die Zahl speichert
// Rufe den Wert ab und gib Folgendes aus: "GenericBox Integer: [value]"
// TODO: Gib eine Leerzeile aus, dann gib Folgendes aus:
// "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 Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – Zahlungssystem9Generics
Einführung in GenericsGenerische KlassenGenerische MethodenGebundene TypparameterWildcards (?, extends, super)Rückblick – Generischer ContainerÜbe selbstständig: Online-Java-Compiler