Summary: in this tutorial, you’ll learn how to use the Dart generics to develop generic classes and methods that work with more than one type.
Introduction to the Dart Generics
Suppose you need to develop a class that represents a pair of values with the same type, for example, a pair of integers or a pair of strings.
To do that, you need to define different classes, one for a pair of integers and another for a pair of strings. For example:
class PairInt {
int x;
int y;
PairInt(this.x, this.y);
}
class PairString {
String x;
String y;
PairString(this.x, this.y);
}
Code language: Dart (dart)
This solution is not scalable especially when you want to have pairs of various types. To resolve this, Dart comes up with the concept of generics.
Generics allow you to parameterize the types for functions, classes, and methods.
For example, the following defines a class that represents a pair of values with the same type:
class Pair<T> {
T x;
T y;
Pair(this.x, this.y);
}
Code language: Dart (dart)
The letter T
inside the angle brackets <>
is the type. By convention, type variables have single-letter names like T
, E
, S
, K
, and V
.
To create a pair of integers, you specify int
as the type T when creating a new Pair
object:
var pairInt = Pair<int>(10, 20);
print('x=${pairInt.x},y=${pairInt.y}');
Code language: Dart (dart)
Output:
x=10,y=20
Code language: Dart (dart)
Similarly, to create a pair of strings, you specify the String
as the type:
var pairStr = Pair<String>('A', 'B');
print('x=${pairStr.x},y=${pairStr.y}');
Code language: Dart (dart)
Output:
x=A,y=B
Code language: Dart (dart)
Here’s the complete program:
class Pair<T> {
T x;
T y;
Pair(this.x, this.y);
}
void main() {
var pairInt = Pair<int>(10, 20);
print('x=${pairInt.x},y=${pairInt.y}');
var pairStr = Pair<String>('A', 'B');
print('x=${pairStr.x},y=${pairStr.y}');
}
Code language: Dart (dart)
In fact, you can use the Pair<T>
class to represent a pair of any object.
In the core library, Dart uses the generics for the collections like List<T>
, Set<T>
, and Map<T>
.
Parameterized type constraints
When you use type T
in a generic class, T
is a subtype of the Object
type. It means that you can access only methods and properties of the Object
type in the generic class.
To specify that T
is a subtype of a particular type, you can use the extends
keyword like this:
class GenericClass<T extends MyClass> {
// ...
}
Code language: Dart (dart)
In this syntax, T
is a subtype of the MyClass
instead of Object
. Therefore, you can access the methods and properties of the MyClass
for any variable declared as T
. For example:
abstract class Shape {
double get area;
}
class Circle extends Shape {
double radius;
Circle({required this.radius});
@override
double get area => 3.14 * radius * radius;
}
class Square extends Shape {
double length;
Square({required this.length});
@override
double get area => length * length;
}
class Region<T extends Shape> {
List<T> shapes;
Region({required this.shapes});
double get area {
double totalArea = 0;
for (var shape in shapes) {
totalArea += shape.area;
}
return totalArea;
}
}
void main() {
var region = Region(
shapes: [Circle(radius: 10),
Square(length: 10),
Square(length: 10)]
);
print(region.area);
}
Code language: Dart (dart)
Output:
514.0
Code language: Dart (dart)
How it works.
First, define the Shape
abstract class and the Circle
and Square
classes that inherit from the Shape
class:
abstract class Shape {
double get area;
}
class Circle extends Shape {
double radius;
Circle({required this.radius});
@override
double get area => 3.14 * radius * radius;
}
class Square extends Shape {
double length;
Square({required this.length});
@override
double get area => length * length;
}
Code language: Dart (dart)
Second, define the Region
generic class with the type T
as the subtype of the Shape
class. Inside the Region
class, the area
getter returns the total area by adding up all the area of all the shapes from the shapes
list:
class Region<T extends Shape> {
List<T> shapes;
Region({required this.shapes});
double get area {
double totalArea = 0;
for (var shape in shapes) {
totalArea += shape.area;
}
return totalArea;
}
}
Code language: Dart (dart)
Third, create a new Region
object and display its total area in the main()
function:
void main() {
var region = Region(
shapes: [Circle(radius: 10),
Square(length: 10),
Square(length: 10)]
);
print(region.area);
}
Code language: Dart (dart)
Summary
- Use generics to define classes and methods that work with more than one type.
- Use
extends
to constraint the type of the generics.