If you look at the API documentation for the basic array type,
List, you'll see that the
type is actually List<E>. The <...> notation marks List as a
generic (or parameterized) type—a type that has formal type
parameters. By convention, most type variables have single-letter names,
such as E, T, S, K, and V.
Why use generics?
#Generics are often required for type safety, but they have more benefits than just allowing your code to run:
- Properly specifying generic types results in better generated code.
- You can use generics to reduce code duplication.
If you intend for a list to contain only strings, you can
declare it as List<String> (read that as "list of string"). That way
you, your fellow programmers, and your tools can detect that assigning a non-string to
the list is probably a mistake. Here's an example:
varnames=<String>[];
names.addAll(['Seth','Kathy','Lars']);
names.add(42);// Error
Another reason for using generics is to reduce code duplication. Generics let you share a single interface and implementation between many types, while still taking advantage of static analysis. For example, say you create an interface for caching an object:
abstractclassObjectCache{
ObjectgetByKey(Stringkey);
voidsetByKey(Stringkey,Objectvalue);
}
You discover that you want a string-specific version of this interface, so you create another interface:
abstractclassStringCache{
StringgetByKey(Stringkey);
voidsetByKey(Stringkey,Stringvalue);
}
Later, you decide you want a number-specific version of this interface... You get the idea.
Generic types can save you the trouble of creating all these interfaces. Instead, you can create a single interface that takes a type parameter:
abstractclassCache<T>{
TgetByKey(Stringkey);
voidsetByKey(Stringkey,Tvalue);
}
In this code, T is the stand-in type. It's a placeholder that you can think of as a type that a developer will define later.
Using collection literals
#
List, set, and map literals can be parameterized. Parameterized literals are
just like the literals you've already seen, except that you add
<type> (for lists and sets) or
<keyType, valueType> (for maps)
before the opening bracket. Here is an example of using typed literals:
varnames=<String>['Seth','Kathy','Lars'];
varuniqueNames=<String>{'Seth','Kathy','Lars'};
varpages=<String,String>{
'index.html':'Homepage',
'robots.txt':'Hints for web robots',
'humans.txt':'We are people, not machines',
};
Using parameterized types with constructors
#
To specify one or more types when using a constructor, put the types in
angle brackets (<...>) just after the class name. For example:
varnameSet=Set<String>.of(names);
The following code creates a SplayTreeMap that has
integer keys and values of type View:
varviews=SplayTreeMap<int,View>();
Generic collections and the types they contain
#Dart generic types are reified, which means that they carry their type information around at runtime. For example, you can test the type of a collection:
varnames=<String>[];
names.addAll(['Seth','Kathy','Lars']);
print(namesisList<String>);// true
Restricting the parameterized type
#
When implementing a generic type,
you might want to limit the types that can be provided as arguments,
so that the argument must be a subtype of a particular type.
This restriction is called a bound.
You can do this using extends.
A common use case is ensuring that a type is non-nullable
by making it a subtype of Object
(instead of the default, Object?).
classFoo<TextendsObject>{
// Any type provided to Foo for T must be non-nullable.
}
You can use extends with other types besides Object.
Here's an example of extending SomeBaseClass,
so that members of SomeBaseClass can be called on objects of type T:
classFoo<TextendsSomeBaseClass>{
// Implementation goes here...
StringtoString()=>"Instance of 'Foo<$T>'";
}
classExtenderextendsSomeBaseClass{
...
}
It's OK to use SomeBaseClass or any of its subtypes as the generic argument:
varsomeBaseClassFoo=Foo<SomeBaseClass>();
varextenderFoo=Foo<Extender>();
It's also OK to specify no generic argument:
varfoo=Foo();
print(foo);// Instance of 'Foo<SomeBaseClass>'
Specifying any non-SomeBaseClass type results in an error:
varfoo=Foo<Object>();
Self-referential type parameter restrictions (F-bounds)
#When using bounds to restrict parameter types, you can refer the bound back to the type parameter itself. This creates a self-referential constraint, or F-bound. For example:
abstractinterfaceclassComparable<T>{
intcompareTo(To);
}
intcompareAndOffset<TextendsComparable<T>>(Tt1,Tt2)=>
t1.compareTo(t2)+1;
classAimplementsComparable<A>{
@override
intcompareTo(Aother)=>/*...implementation...*/0;
}
intuseIt=compareAndOffset(A(),A());
The F-bound T extends Comparable<T> means T must be comparable to itself.
So, A can only be compared to other instances of the same type.
Using generic methods
#Methods and functions also allow type arguments:
Tfirst<T>(List<T>ts){
// Do some initial work or error checking, then...
Ttmp=ts[0];
// Do some additional checking or processing...
returntmp;
}
Here the generic type parameter on first (<T>)
allows you to use the type argument T in several places:
- In the function's return type (
T). - In the type of an argument (
List<T>). - In the type of a local variable (
T tmp).
Unless stated otherwise, the documentation on this site reflects Dart 3.12.2. Page last updated on 2025-07-03. View source or report an issue.
