Generics
21.1 Generic Classes and Methods
class Box<T> {
T value;
Box(this.value);
R map<R>(R Function(T) transform) => transform(value);
}
class Stack<E> {
final _items = <E>[];
void push(E item) => _items.add(item);
E pop() {
if (_items.isEmpty) throw StateError('Stack is empty');
return _items.removeLast();
}
E? get peek => _items.isEmpty ? null : _items.last;
bool get isEmpty => _items.isEmpty;
}
T firstOrDefault<T>(List<T> items, T fallback) => items.isEmpty ? fallback : items.first;
void main() {
final s = Stack<int>()..push(1)..push(2);
print(s.pop());
final box = Box('hello'); // Box<String> inferred
print(box.map((v) => v.length));
print(firstOrDefault<String>([], 'none'));
}21.2 Bounded Type Parameters
T maxOf<T extends Comparable<T>>(List<T> items) =>
items.reduce((a, b) => a.compareTo(b) >= 0 ? a : b);
class Repository<T extends Entity> {
final Map<String, T> _store = {};
void save(T entity) => _store[entity.id] = entity;
T? find(String id) => _store[id];
}
abstract class Entity { String get id; }
num sumAll<T extends num>(Iterable<T> values) => values.fold<num>(0, (a, b) => a + b);21.3 Reified Generics and Variance
Dart generics are reified: type arguments are available at runtime (list is List<int> works), unlike Java's erasure.
Generic classes are covariant by default (List<int> is a List<num>). That's sound thanks to runtime checks: adding a double to a List<int> seen as List<num> throws. The covariant keyword can relax parameter types in overrides.
List<num> numbers = <int>[1, 2, 3];
// numbers.add(1.5); // runtime TypeError (covariance check)
print(<int>[] is List<num>); // true