Generic Constraints
Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 67 of 110.
Sometimes you need a generic type to have certain capabilities. For example, if you want to compare items or access specific properties, an unrestricted T won't guarantee those features exist. Generic constraints let you restrict type parameters to specific types or their subclasses.
Use the extends keyword to constrain a type parameter:
class NumberBox<T extends num> {
T value;
NumberBox(this.value);
T doubled() => (value * 2) as T;
}
void main() {
var intBox = NumberBox<int>(5);
var doubleBox = NumberBox<double>(3.5);
print(intBox.doubled()); // 10
print(doubleBox.doubled()); // 7.0
// var stringBox = NumberBox<String>('hi'); // Error!
}Because T extends num, the compiler knows value supports numeric operations like multiplication. Without this constraint, calling value * 2 would fail.
Constraints work with your own class hierarchies too:
abstract class Animal {
String get name;
}
class Dog extends Animal {
String get name => 'Dog';
}
class Shelter<T extends Animal> {
List<T> animals = [];
void add(T animal) => animals.add(animal);
void printNames() {
for (var animal in animals) {
print(animal.name); // Safe: T guarantees 'name' exists
}
}
}Generic constraints combine the flexibility of generics with the safety of knowing what operations are available on your type.
Challenge
EasyLet's build a statistics calculator that only works with numeric types! You'll create a generic class with a constraint that ensures it can only be used with numbers, allowing you to safely perform mathematical operations on the stored values.
You'll organize your code into two files:
stats.dart: Create a genericStats<T extends num>class that manages a list of numeric values:- A
List<T>calledvaluesto store the numbers - A constructor that initializes the list with provided values
- A method
sum()that returns the sum of all values as anum - A method
min()that returns the smallest value asT - A method
max()that returns the largest value asT - A method
printStats()that displays the statistics in a formatted way
- A
main.dart: Import your stats file and demonstrate the constrained generic class with different numeric types:- Create a
Stats<int>with values[10, 25, 5, 30, 15]and callprintStats() - Print an empty line
- Create a
Stats<double>with values[3.5, 1.2, 4.8, 2.1]and callprintStats()
- Create a
The printStats() method should output three lines showing the sum, minimum, and maximum values. Because T extends num, you can safely use comparison operators and arithmetic operations on the values - something that wouldn't be possible with an unconstrained generic type.
Expected output:
Sum: 85
Min: 5
Max: 30
Sum: 11.6
Min: 1.2
Max: 4.8Cheat sheet
Generic constraints restrict type parameters to specific types or their subclasses using the extends keyword:
class NumberBox<T extends num> {
T value;
NumberBox(this.value);
T doubled() => (value * 2) as T;
}With T extends num, the compiler knows value supports numeric operations like multiplication.
Constraints also work with custom class hierarchies:
abstract class Animal {
String get name;
}
class Shelter<T extends Animal> {
List<T> animals = [];
void printNames() {
for (var animal in animals) {
print(animal.name); // Safe: T guarantees 'name' exists
}
}
}Generic constraints provide flexibility while ensuring type safety and available operations.
Try it yourself
import 'stats.dart';
void main() {
// TODO: Create a Stats<int> with values [10, 25, 5, 30, 15]
// and call printStats()
// TODO: Print an empty line
// TODO: Create a Stats<double> with values [3.5, 1.2, 4.8, 2.1]
// and call printStats()
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor