Skip to main content
A generic class is a class declaration that accepts one or more formal type parameters, allowing the definition of properties and methods to remain abstract regarding the specific data types they manipulate until instantiation. This mechanism enforces type safety and enables parametric polymorphism.

Declaration Syntax

A generic class is defined by appending a comma-separated list of type parameters enclosed in angle brackets (< >) immediately after the class name. By convention, single-letter identifiers such as E (Element), T (Type), K (Key), and V (Value) are used.
class Container<T> {
  T _value;

  Container(this._value);

  T getValue() {
    return _value;
  }

  void updateValue(T newValue) {
    _value = newValue;
  }
}
Multiple type parameters can be defined as follows:
class Pair<K, V> {
  final K key;
  final V value;

  Pair(this.key, this.value);
}

Instantiation

When instantiating a generic class, the caller provides concrete types (type arguments) to replace the formal type parameters. Explicit Type Argumentation: The type is explicitly declared in angle brackets.
// T becomes int
var intContainer = Container<int>(10);

// K becomes String, V becomes int
var entry = Pair<String, int>('ID', 404);
Type Inference: If the constructor arguments provide sufficient context, Dart infers the type arguments.
// Dart infers Container<String> based on the argument 'Hello'
var stringContainer = Container('Hello'); 

Restricted Type Parameters (Bounds)

Type parameters can be restricted to a specific type hierarchy using the extends keyword. This ensures that the provided type argument is a subtype of a specific class or interface.
// T must be num or a subtype of num (e.g., int, double)
class NumericProcessor<T extends num> {
  T process(T input) {
    // Methods of 'num' are available here
    print(input.abs()); 
    return input;
  }
}

void main() {
  var processor = NumericProcessor<double>(); // Valid
  // var error = NumericProcessor<String>(); // Compile-time error
}

Reified Types

Unlike languages that use type erasure (such as Java), Dart generic types are reified. This means that type information is retained and accessible at runtime. You can perform type checks against type parameters within the class or on instances of the class.
var names = <String>[];
print(names is List<String>); // true
print(names.runtimeType); // List<String>

Generic Covariance

Dart generic classes are covariant by default. If TypeA is a subtype of TypeB, then GenericClass<TypeA> is considered a subtype of GenericClass<TypeB>.
class Box<T> {}

void main() {
  Box<int> intBox = Box<int>();
  
  // Valid because int is a subtype of Object
  Box<Object> objBox = intBox; 
}
Master Dart with Deep Grasping Methodology!Learn More