Skip to main content

Generics


Why Generics?

// Without generics — lose type safety
List items = [1, 'hello', true]; // List<dynamic>
var first = items[0]; // dynamic — no type info

// With generics — type safe
List<int> numbers = [1, 2, 3];
var n = numbers[0]; // int — full type info and IDE support
// numbers.add('hello'); // ❌ compile error

Generic Classes

class Box<T> {
T value;
Box(this.value);

Box<R> map<R>(R Function(T) transform) => Box(transform(value));


String toString() => 'Box<$T>($value)';
}

var intBox = Box(42); // Box<int>
var strBox = Box('hello'); // Box<String>
var doubled = intBox.map((n) => n * 2); // Box<int>
var asString = intBox.map((n) => '$n'); // Box<String>

print(intBox); // Box<int>(42)
print(doubled); // Box<int>(84)
print(asString); // Box<String>(42)

Generic Methods

// Generic function
T first<T>(List<T> list) => list.first;
T? firstOrNull<T>(List<T> list) => list.isEmpty ? null : list.first;

List<T> repeat<T>(T item, int times) =>
List.generate(times, (_) => item);

// Multiple type parameters
Map<K, V> zip<K, V>(List<K> keys, List<V> values) =>
Map.fromIterables(keys, values);

void swap<T>(List<T> list, int i, int j) {
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}

Bounded Type Parameters

// T extends Comparable — T must implement Comparable
T max<T extends Comparable<T>>(T a, T b) => a.compareTo(b) >= 0 ? a : b;

print(max(3, 7)); // 7
print(max('apple', 'mango')); // mango

// T extends a class
class Repository<T extends Entity> {
final List<T> _items = [];

void add(T item) => _items.add(item);

T? findById(int id) =>
_items.where((item) => item.id == id).firstOrNull;
}

abstract class Entity {
int get id;
}

Generic Stack Example

class Stack<T> {
final _items = <T>[];

void push(T item) => _items.add(item);

T pop() {
if (isEmpty) throw StateError('Stack is empty');
return _items.removeLast();
}

T peek() {
if (isEmpty) throw StateError('Stack is empty');
return _items.last;
}

bool get isEmpty => _items.isEmpty;
int get size => _items.length;


String toString() => 'Stack($_items)';
}

var stack = Stack<int>();
stack.push(1);
stack.push(2);
stack.push(3);
print(stack); // Stack([1, 2, 3])
print(stack.pop()); // 3
print(stack); // Stack([1, 2])

Covariance & Contravariance

// Generics are invariant by default in Dart
List<Dog> dogs = [Dog(), Dog()];
// List<Animal> animals = dogs; // ❌ not allowed!

// But you can use the supertype
List<Animal> animals = List<Animal>.from(dogs); // OK — creates new list

// Covariant keyword (use carefully)
class Kennel<covariant T extends Animal> {
T animal;
Kennel(this.animal);
}

Kennel<Dog> dogKennel = Kennel(Dog());
Kennel<Animal> animalKennel = dogKennel; // OK with covariant