Wenn Sie die API-Dokumentation des grundlegenden Array-Typs List einsehen, stellen Sie fest, dass sein eigentlicher Typ List<E> ist. Spitzklammern wie <...> kennzeichnen einen generischen Typ (parametrisierter Typ) – also einen Typ mit formalen Typ Parametern. Der Konvention nach werden nahezu alle Typvariablen mit einem einzelnen Buchstaben benannt, zum Beispiel E, T, S, K, V.
Warum Generika verwenden?
- Der zentrale Vorteil von Generika ist die Gewährleistung von Typsicherheit
- Korrekt spezifizierte Generika erzeugen optimierten Laufzeitcode;
- Generika reduzieren Codeduplizierung drastisch.
Wenn eine Liste ausschließlich Zeichenketten speichern soll, deklarieren Sie sie als List<String> – dies ist eine Zeichenkettenliste.
Sobald eine Liste als zeichenkettenbasiert definiert ist, löst das Einfügen von Werten anderer Typen einen Fehler aus.
// Statische Typüberprüfung gibt Fehler aus
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Kompilierfehler: 42 ist keine ZeichenketteCode-Sprache: PHP (php)
D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:5:12: Error: The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42); // Kompilierfehler: 42 ist keine ZeichenketteCode-Sprache: PHP (php)
Beseitigung von Code-Redundanz
Angenommen, Sie benötigen eine abstrakte Schnittstelle für einen Objekt-Cache:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
Code-Sprache: JavaScript (javascript)
Später, falls Sie einen Cache brauchen, der nur Zeichenketten speichert, müssen Sie eine separate Schnittstelle erstellen:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Code-Sprache: Dart (dart)
Anschließend benötigen Sie spezielle Caches für Zahlen… Sie müssen nahezu identischen Code immer wieder schreiben.
Beispielsweise würden Sie folgenden Code verfassen:
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}Code-Sprache: Dart (dart)
Generika lösen dieses Problem mit einer einzigen Definition einer universellen Schnittstelle mit Typ-Parametern:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}Code-Sprache: Dart (dart)
Hier fungiert T als Typ-Platzhalter für beliebige konkrete Typen, die später vom Entwickler übergeben werden.
Praktisches Beispiel
// Generische abstrakte Cache-Basisklasse
abstract class Cache<T> {
T? getByKey(String key);
void setByKey(String key, T value);
void remove(String key);
}
// Speichercache-Implementierung (basiert auf Map)
class MemoryCache<T> implements Cache<T> {
final Map<String, T> _storage = {};
@override
T? getByKey(String key) {
return _storage[key];
}
@Override
void setByKey(String key, T value) {
_storage[key] = value;
}
@Override
void remove(String key) {
_storage.remove(key);
}
}
void main() {
// 1. Beispiel Zeichenketten-Cache
final Cache<String> strCache = MemoryCache<String>();
strCache.setByKey("name", "Tom");
print(strCache.getByKey("name")); // Tom
// 2. Ganzzahl-Int-Cache
final Cache<int> intCache = MemoryCache<int>();
intCache.setByKey("age", 26);
print(intCache.getByKey("age")); // 26
// 3. Cache für benutzerdefinierte Objekte
final Cache<User> userCache = MemoryCache<User>();
userCache.setByKey("u1", User("Jack", 18));
final User? user = userCache.getByKey("u1");
print(user?.name); // Jack
// Löschtest
userCache.remove("u1");
print(userCache.getByKey("u1")); // null
}
// Benutzerdefinierte Entitätsklasse
class User {
final String name;
final int age;
User(this.name, this.age);
}Code-Sprache: Dart (dart)

Verwendung generischer Collection-Literale
List-, Set- und Map-Literale unterstützen alle parametrisierte Generika:
- List / Set: Schreiben Sie
<Typ>vor die öffnende eckige Klammer - Map: Schreiben Sie
<Schlüsseltyp, Werttyp>vor die öffnende geschweifte Klammer
Beispiele:
// Zeichenkettenliste
var names = <String>['Seth', 'Kathy', 'Lars'];
// Zeichenkettenmenge (automatische Duplikatentfernung)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
// Wörterbuch mit Zeichenkettenschlüsseln und -werten
var pages = <String, String>{
'index.html': 'Startseite',
'robots.txt': 'Anweisungsdatei für Webcrawler',
'humans.txt': 'Dokumentationsdatei für menschliche Entwickler',
};
Code-Sprache: PHP (php)
Ausführen des Beispielcodes
void main() {
// 1. List<String> Zeichenkettenliste: geordnet, Duplikate erlaubt, zugreifbar über Index
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
print("=== List Liste ===");
print(names); // [Seth, Kathy, Lars, Seth] Duplikate zulässig
print(names[0]); // Erstes Element per Index abrufen: Seth
names.add("Mike"); // Neues Element am Ende anhängen
print("Nach Hinzufügen: $names");
// 2. Set<String> Zeichenkettenmenge: ungeordnet, automatische Duplikatentfernung, kein Index
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
print("\n=== Set Menge (automatische Duplikatentfernung) ===");
print(uniqueNames); // {Seth, Kathy, Lars} Doppeltes Seth wird automatisch gelöscht
print(uniqueNames.contains("Kathy")); // Existenzprüfung: true
uniqueNames.remove("Lars");
print("Nach Löschen von Lars: $uniqueNames");
// 3. Map<String, String> Schlüssel-Wert-Wörterbuch: eindeutige Schlüssel, Wertabruf per Schlüssel
var pages = <String, String>{
'index.html': 'Startseite',
'robots.txt': 'Anweisungsdatei für Webcrawler',
'humans.txt': 'Dokumentationsdatei für menschliche Entwickler',
};
print("\n=== Map Wörterbuch ===");
print(pages);
print(pages['index.html']); // Wert per Schlüssel abrufen: Startseite
pages["about.html"] = "Über-Seite"; // Neues Schlüssel-Wert-Paar hinzufügen
print("Nach Hinzufügen von about.html: $pages");
// Häufige Iterationsbeispiele
print("\n=== Iteriere über List ===");
for (var name in names) {
print("- $name");
}
print("\n=== Iteriere über alle Map-Einträge ===");
for (var entry in pages.entries) {
print("Datei: ${entry.key} → Beschreibung: ${entry.value}");
}
}Code-Sprache: Dart (dart)
Generische Parameter in Konstruktoren
Beim Aufruf eines Konstruktors geben Sie den generischen Typ in Spitzklammern hinter dem Klassennamen an:
var nameSet = Set<String>.of(names);Code-Sprache: JavaScript (javascript)
Der folgende Code erstellt ein Wörterbuch mit int-Schlüsseln und Werten des Typs View:
var views = SplayTreeMap<int, View>();
Code-Sprache: HTML, XML (xml)
import 'dart:collection';
void main() {
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
var nameSet = Set<String>.of(names);
print("Ursprüngliche Liste: $names");
print("Duplikatfreies Set: $nameSet");
// SplayTreeMap erfordert Import von dart:collection
var views = SplayTreeMap<int, String>();
views[5] = "Einstellungsseite";
views[2] = "Startseite";
views[9] = "Meine Seite";
views[1] = "Splashscreen";
print("\nSortierte TreeMap:");
for (var e in views.entries) {
print("${e.key} : ${e.value}");
}
}Code-Sprache: PHP (php)
Laufzeittypen generischer Collections
Typ-Reifikation (reified)
Darts Generika sind reifiziert: Sämtliche generische Typinformationen bleiben zur Laufzeit erhalten.
Sie können den konkreten generischen Typ einer Collection zur Laufzeit direkt prüfen:
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // Gibt true aus
}Code-Sprache: PHP (php)
Zusätzlicher Vergleich: Java nutzt Typ-Löschung (Type Erasure) für Generika; generische Parameter werden zur Laufzeit entfernt. In Java lässt sich nur prüfen, ob ein Objekt eine
Listist – man kann nicht zwischenList<String>undList<int>unterscheiden.
* Einschränkung generischer Parametertypen
Bound (Typgrenze). Wenn Sie Klassen noch nicht gelernt haben, reicht ein grundlegendes Verständnis dieses Abschnitts aus.
Bei der Definition von Generika können Sie einschränken, dass übergebene Typen Unterklassen einer bestimmten Oberklasse sein müssen. Diese Einschränkung heißt Typgrenze und wird mit dem Schlüsselwort extends realisiert.
1 Einschränkung auf nicht-nullbare Typen
Erzwingen, dass das Generikum nur nicht-nullbare Typen akzeptiert (Standard-Obergrenze ist der nullbare Typ Object?):
class Foo<T extends Object> {
// Der übergebene Typ T darf nicht nullbar sein
}
Code-Sprache: JavaScript (javascript)
2 Begrenzung auf Unterklassen einer festgelegten Oberklasse
Geben Sie mit extends eine Basisklasse an, dann können Sie alle Member-Methoden dieser Basisklasse direkt aufrufen:
class Foo<T extends SomeBaseClass> {
String toString() => "Instanz Foo<$T>";
}
class Extender extends SomeBaseClass {}
Code-Sprache: JavaScript (javascript)
Gültige Typargumente: die Oberklasse selbst, beliebige ihrer Unterklassen
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Code-Sprache: HTML, XML (xml)
Wird der generische Parameter weggelassen, wird standardmäßig der Grenztyp verwendet:
var foo = Foo();
print(foo); // Ausgabe: Instanz Foo<SomeBaseClass>
Code-Sprache: PHP (php)
Die Übergabe eines unzusammenhängenden Typs löst einen statischen Kompilierfehler aus:
var foo = Foo<Object>(); // Kompilierfehler: Object ist keine Unterklasse von SomeBaseClass
Code-Sprache: JavaScript (javascript)
* Selbstreferenzierende Typgrenzen (F-bound generische Typen)
Bei der Einschränkung von Generika kann die Grenze auf ihren eigenen Typparameter verweisen und eine Selbstbeschränkung bilden – dies wird F-bound genannt.
Standardbeispiel: Vergleichs-Schnittstelle Comparable<T>
abstract interface class Comparable<T> {
int compareTo(T o);
}
// T muss Comparable<T> implementieren und kann nur mit Objekten desselben Typs verglichen werden
int compareAndOffset<T extends Comparable<T>>(T t1, T t2) =>
t1.compareTo(t2) + 1;
class A implements Comparable<A> {
@override
int compareTo(A other) {
// Vergleichslogik implementieren
return 0;
}
}
int res = compareAndOffset(A(), A());
Code-Sprache: PHP (php)
Die Einschränkung T extends Comparable<T> bedeutet: T kann nur mit Instanzen des identischen Typs verglichen werden.
* Generische Methoden
Normale Funktionen und Klassenmethoden unterstützen ebenfalls generische Typ-Parameter:
T first<T>(List<T> ts) {
// Vorprüfungslogik
T tmp = ts[0];
// Nachverarbeitungslogik
return tmp;
}
Code-Sprache: PHP (php)
Vollständiges Beispiel
T first<T>(List<T> ts) {
if (ts.isEmpty) {
throw ArgumentError("Die Liste darf nicht leer sein");
}
T tmp = ts[0];
return tmp;
}
void main() {
List<String> strList = ["apple", "banana", "orange"];
String firstStr = first(strList);
print(firstStr); //apple
List<int> numList = [10, 20, 30];
int firstNum = first(numList);
print(firstNum);//10
}Code-Sprache: Dart (dart)
Der generische Parameter <T> wirkt an drei Stellen:
- Rückgabetyp der Funktion
T - Typ des Eingabeparameters
List<T> - Typ der lokalen Variable
T tmp