Generics
Writing type-safe, reusable classes and functions with generic type parameters.
2 menit membaca
You've already used generics constantly without naming them — every List<String> and Map<String, int> is a generic type in action. This lesson is about writing your own generic classes and functions, so you can get that same type safety and reuse in your own code.
The problem generics solve
Imagine a Box class meant to hold a single value of any type. Without generics, you'd either write one class per type, or use Object and lose type safety entirely:
class ObjectBox {
Object value;
ObjectBox(this.value);
}
final box = ObjectBox(42);
int number = box.value as int; // works, but only because you cast it correctly by handNothing here stops box.value from later holding a String instead, and the cast is a runtime risk the compiler can't check for you.
Generic classes
A type parameter, conventionally named T, fixes this — the class is defined once, but each usage locks in a specific, checked type:
class Box<T> {
T value;
Box(this.value);
void show() => print('Box contains: $value');
}
void main() {
final intBox = Box<int>(42);
final stringBox = Box<String>('hello');
intBox.show(); // Box contains: 42
stringBox.show(); // Box contains: hello
int number = intBox.value; // no cast needed — already known to be int
// intBox.value = 'oops'; // compile error: String isn't int
}Box<int> and Box<String> are both built from the same Box<T> definition, but the compiler treats them as distinct, fully type-checked variants — you get the reuse of writing the logic once, with none of the safety cost of Object.
Generic functions
Type parameters work on standalone functions too:
T firstElement<T>(List<T> items) {
if (items.isEmpty) throw StateError('List is empty');
return items.first;
}
void main() {
print(firstElement<int>([1, 2, 3])); // 1
print(firstElement<String>(['a', 'b', 'c'])); // a
}Dart can usually infer T from the argument, so firstElement([1, 2, 3]) works identically without writing <int> explicitly — you only need it when the type can't be inferred from context.
Bounded type parameters
Sometimes a generic needs to guarantee its type has certain capabilities — extends on a type parameter restricts which types are allowed:
class NumberBox<T extends num> {
T value;
NumberBox(this.value);
double get doubled => value * 2;
}
final box = NumberBox<int>(5);
print(box.doubled); // 10.0
// NumberBox<String>('x'); // compile error: String doesn't extend numWithout extends num, value * 2 wouldn't compile inside the class — the compiler has no way to know T supports multiplication unless you tell it what T is guaranteed to be.
Where you'll actually use this
You'll rarely write more than a handful of generic classes in typical application code — most of the time, you're a consumer of generics (List<T>, Map<K, V>, Future<T>) rather than an author of new ones. But recognizing <T> and knowing what it buys you is essential the moment you read almost any real Dart codebase, since the standard library leans on it everywhere.