Dart Generics

If you check the API documentation for the basic array type List, you will find its actual type is List<E>. Angle brackets like <...> denote a generic type (parameterized type) — a type with formal type parameters. By convention, nearly all type variables are named with a single letter, such as E, T, S, K, V

Why Use Generics?

  • The core benefit of generics is enforcing type safety
  • Properly specified generics produce more optimized runtime code;
  • Generics drastically reduce code duplication.

If you want a list to only store strings, declare it as List<String> — this is a string list.

Once defined as a string-only list, inserting non-string values will trigger an error.

// Static type checking error
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Compile error: 42 is not a StringCode language: 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); // Compile error: 42 is not a StringCode language: PHP (php)

Eliminate Code Redundancy

Suppose you need an abstract interface for an object cache:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}
Code language: JavaScript (javascript)

Later, if you require a cache that only stores strings, you must create a separate interface:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}
Code language: Dart (dart)

You will then need dedicated caches for numbers… forcing repetitive writing of nearly identical code.

For example, you would end up writing something like this:

abstract class IntCache {
  int getByKey(String key);
  void setByKey(String key, int value);
}Code language: Dart (dart)

Generics solve this problem in one go by letting you define a single universal interface with type parameters:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}Code language: Dart (dart)

Here, T acts as a type placeholder representing any concrete type passed in by developers later.

Practical Example

// Generic abstract cache base class
abstract class Cache<T> {
  T? getByKey(String key);
  void setByKey(String key, T value);
  void remove(String key);
}

// In-memory cache implementation (backed by 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. String cache example
  final Cache<String> strCache = MemoryCache<String>();
  strCache.setByKey("name", "Tom");
  print(strCache.getByKey("name")); // Tom

  // 2. Integer cache
  final Cache<int> intCache = MemoryCache<int>();
  intCache.setByKey("age", 26);
  print(intCache.getByKey("age")); // 26

  // 3. Custom object cache
  final Cache<User> userCache = MemoryCache<User>();
  userCache.setByKey("u1", User("Jack", 18));
  final User? user = userCache.getByKey("u1");
  print(user?.name); // Jack

  // Deletion test
  userCache.remove("u1");
  print(userCache.getByKey("u1")); // null
}

// Custom entity class
class User {
  final String name;
  final int age;
  User(this.name, this.age);
}Code language: Dart (dart)

Using Generic Collection Literals

List, Set, and Map literals all support parameterized generics:

  • List / Set: Write <Type> before the opening bracket
  • Map: Write <KeyType, ValueType> before the opening curly brace

Examples:

// String list
var names = <String>['Seth', 'Kathy', 'Lars'];

// String set (auto-duplicate removal)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};

// Dictionary with string keys and string values
var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Crawler instruction file',
  'humans.txt': 'Documentation file for human developers',
};
Code language: PHP (php)

Run the Sample Code

void main() {
  // 1. List<String> String List: ordered, allows duplicates, index-accessible
  var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
  print("=== List ===");
  print(names); // [Seth, Kathy, Lars, Seth] duplicates permitted
  print(names[0]); // Get first element by index: Seth
  names.add("Mike"); // Append new element
  print("After addition: $names");

  // 2. Set<String> String Set: unordered, auto deduplication, no indexes
  var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
  print("\n=== Set (Auto Duplicate Removal) ===");
  print(uniqueNames); // {Seth, Kathy, Lars} duplicate Seth removed automatically
  print(uniqueNames.contains("Kathy")); // Existence check: true
  uniqueNames.remove("Lars");
  print("After deleting Lars: $uniqueNames");

  // 3. Map<String, String> Key-value dictionary: unique keys, retrieve via key
  var pages = <String, String>{
    'index.html': 'Homepage',
    'robots.txt': 'Crawler instruction file',
    'humans.txt': 'Documentation file for human developers',
  };
  print("\n=== Map Dictionary ===");
  print(pages);
  print(pages['index.html']); // Retrieve value by key: Homepage
  pages["about.html"] = "About Page"; // Add new key-value pair
  print("After adding about.html: $pages");

  // Common iteration examples
  print("\n=== Iterate List ===");
  for (var name in names) {
    print("- $name");
  }

  print("\n=== Iterate All Map Entries ===");
  for (var entry in pages.entries) {
    print("File: ${entry.key} → Description: ${entry.value}");
  }
}Code language: Dart (dart)

Generic Parameters in Constructors

When calling a constructor, specify generic types inside angle brackets after the class name:

var nameSet = Set<String>.of(names);Code language: JavaScript (javascript)

The code below creates a dictionary with int keys and View values:

var views = SplayTreeMap<int, View>();
Code language: HTML, XML (xml)
import 'dart:collection';

void main() {
  var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
  var nameSet = Set<String>.of(names);
  print("Original List: $names");
  print("Deduplicated Set: $nameSet");

  // SplayTreeMap requires dart:collection import
  var views = SplayTreeMap<int, String>();
  views[5] = "Settings Page";
  views[2] = "Homepage";
  views[9] = "My Page";
  views[1] = "Splash Screen";

  print("\nOrdered TreeMap:");
  for (var e in views.entries) {
    print("${e.key} : ${e.value}");
  }
}Code language: PHP (php)

Runtime Types of Generic Collections

Type Reification

Dart generics are reified: full generic type information is preserved at runtime.

You can directly check the concrete generic type of a collection during runtime:

void main() {
    var names = <String>[];
	names.addAll(['Seth', 'Kathy', 'Lars']);
	print(names is List<String>); // Outputs true
}Code language: PHP (php)

Additional comparison: Java uses type erasure for generics, stripping generic parameters at runtime. In Java you can only verify an object is a List, and cannot distinguish between List<String> and List<int>.

* Restricting Generic Parameter Types

Bounds. Skip deep study for now if you haven’t learned classes yet; a basic understanding suffices.

When defining generics, you can restrict passed types to subclasses of a certain supertype. This constraint is called a type bound, implemented via the extends keyword.

1 Restrict to Non-Nullable Types

Force generics to accept only non-nullable types (default upper bound is nullable Object?):

class Foo<T extends Object> {
  // T cannot be a nullable type
}
Code language: JavaScript (javascript)

2 Restrict to Subclasses of a Specified Superclass

Use extends to specify a base class, letting you directly call all member methods of the base class:

class Foo<T extends SomeBaseClass> {
  String toString() => "Instance Foo<$T>";
}

class Extender extends SomeBaseClass {}
Code language: JavaScript (javascript)

Valid type arguments: the base class itself, or any of its subclasses

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Code language: HTML, XML (xml)

Omitted generic parameters default to the bound type:

var foo = Foo();
print(foo); // Output: Instance Foo<SomeBaseClass>
Code language: PHP (php)

Passing an unrelated type triggers a static compile error:

var foo = Foo<Object>(); // Compile error: Object is not a subclass of SomeBaseClass
Code language: JavaScript (javascript)

* Self-Referential Type Bounds (F-Bounded Generics)

When constraining generics, bounds may reference their own type parameter to create self-constraints, known as F-bounds.

Standard example: Comparable interface Comparable<T>

abstract interface class Comparable<T> {
  int compareTo(T o);
}

// T must implement Comparable<T>, only comparable with instances of the same type
int compareAndOffset<T extends Comparable<T>>(T t1, T t2) =>
    t1.compareTo(t2) + 1;

class A implements Comparable<A> {
  @override
  int compareTo(A other) {
    // Implement comparison logic
    return 0;
  }
}

int res = compareAndOffset(A(), A());
Code language: PHP (php)

The constraint T extends Comparable<T> means: T may only compare against instances of the identical type.

* Generic Methods

Regular standalone functions and class methods also support generic type parameters:

T first<T>(List<T> ts) {
  // Pre-check logic
  T tmp = ts[0];
  // Post-processing logic
  return tmp;
}
Code language: PHP (php)

Full Example

T first<T>(List<T> ts) {
  if (ts.isEmpty) {
    throw ArgumentError("List cannot be empty");
  }
  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 language: Dart (dart)

The generic parameter <T> applies to three locations:

  1. Function return type T
  2. Input parameter type List<T>
  3. Local variable type T tmp

Previous:

Leave a Reply

Your email address will not be published. Required fields are marked *