Skip to content

Generics

Generics allow you to write type-safe, reusable code that works with different types.

Why Generics?

dart
// Without generics - not type-safe
class Box {
  Object? content;
  
  void put(Object item) {
    content = item;
  }
  
  Object? get() {
    return content;
  }
}

// With generics - type-safe
class GenericBox<T> {
  T? content;
  
  void put(T item) {
    content = item;
  }
  
  T? get() {
    return content;
  }
}

void main() {
  var intBox = GenericBox<int>();
  intBox.put(42);
  // intBox.put('text');  // Error: type mismatch
  
  var stringBox = GenericBox<String>();
  stringBox.put('Hello');
  print(stringBox.get());  // Hello
}

Generic Collections

dart
// List
List<int> numbers = [1, 2, 3];
List<String> names = ['Alice', 'Bob'];

// Map
Map<String, int> ages = {'Alice': 25, 'Bob': 30};

// Set
Set<String> colors = {'red', 'green', 'blue'};

Generic Functions

dart
T getFirst<T>(List<T> list) {
  return list[0];
}

void main() {
  print(getFirst<int>([1, 2, 3]));        // 1
  print(getFirst<String>(['a', 'b']));    // a
  print(getFirst([1.5, 2.5]));            // 1.5 (type inferred)
}

Generic Classes

dart
class Pair<K, V> {
  K key;
  V value;
  
  Pair(this.key, this.value);
  
  @override
  String toString() => 'Pair($key, $value)';
}

void main() {
  var pair1 = Pair<String, int>('age', 25);
  var pair2 = Pair<int, String>(1, 'first');
  
  print(pair1);  // Pair(age, 25)
  print(pair2);  // Pair(1, first)
}

Type Constraints

dart
class NumberBox<T extends num> {
  T value;
  
  NumberBox(this.value);
  
  T add(T other) {
    return (value + other) as T;
  }
}

void main() {
  var intBox = NumberBox<int>(10);
  print(intBox.add(5));  // 15
  
  var doubleBox = NumberBox<double>(10.5);
  print(doubleBox.add(2.5));  // 13.0
  
  // var stringBox = NumberBox<String>('text');  // Error!
}

Generic Methods

dart
class Utils {
  static T? findFirst<T>(List<T> list, bool Function(T) test) {
    for (var item in list) {
      if (test(item)) {
        return item;
      }
    }
    return null;
  }
}

void main() {
  var numbers = [1, 2, 3, 4, 5];
  var firstEven = Utils.findFirst<int>(numbers, (n) => n % 2 == 0);
  print(firstEven);  // 2
  
  var names = ['Alice', 'Bob', 'Charlie'];
  var longName = Utils.findFirst<String>(names, (n) => n.length > 5);
  print(longName);  // Charlie
}

Complete Example

dart
class Stack<T> {
  final List<T> _items = [];
  
  void push(T item) {
    _items.add(item);
  }
  
  T? pop() {
    if (_items.isEmpty) return null;
    return _items.removeLast();
  }
  
  T? peek() {
    if (_items.isEmpty) return null;
    return _items.last;
  }
  
  bool get isEmpty => _items.isEmpty;
  int get size => _items.length;
  
  @override
  String toString() => _items.toString();
}

void main() {
  var intStack = Stack<int>();
  intStack.push(1);
  intStack.push(2);
  intStack.push(3);
  
  print('Stack: $intStack');      // [1, 2, 3]
  print('Pop: ${intStack.pop()}'); // 3
  print('Peek: ${intStack.peek()}'); // 2
  print('Size: ${intStack.size}'); // 2
}

Next Steps

Content is for learning and research only.