如果你查看基础数组类型 List 的 API 文档,会发现它的真正的类型其实是 List<E>。<...> 这种尖括号标记代表这是一个泛型类型(参数化类型), 带有形式类型参数的类型。 按照惯例,绝大多数类型变量都使用单个字母命名,例如 E、T、S、K、V
为什么要使用泛型?
- 泛型最核心的作用是保障类型安全
- 正确指定泛型可以生成更优的运行代码;
- 使用泛型能大幅减少代码重复。
如果你希望一个列表只存放字符串,就可以声明为 List<String>,这就是字符串列表。
一旦定义了这个是字符串的列表,如果往它里放入非字符串类型属于错误操作。
// 静态类型检查报错
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // 编译错误:42 不是字符串Code 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); // 编译错误:42 不是字符串Code language: PHP (php)
减少代码冗余
假设你需要一个缓存对象的抽象接口:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
Code language: JavaScript (javascript)
后续你又需要仅存储字符串的缓存接口,只能再新建一套:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
Code language: Dart (dart)
之后还会需要数字专用缓存…… 不断重复编写几乎一样的代码。
例如你可能会这样:
abstract class IntCache {
int getByKey(String key);
void setByKey(String key, int value);
}Code language: Dart (dart)
泛型可以一次性解决这个问题,只需要定义一个带类型参数的通用接口:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}Code language: Dart (dart)
这里的 T 是占位类型,代表开发者后续传入的任意实际类型。
具体例子
// 泛型抽象缓存基类
abstract class Cache<T> {
T? getByKey(String key);
void setByKey(String key, T value);
void remove(String key);
}
// 内存缓存实现(基于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. 字符串缓存示例
final Cache<String> strCache = MemoryCache<String>();
strCache.setByKey("name", "Tom");
print(strCache.getByKey("name")); // Tom
// 2. 数字int缓存
final Cache<int> intCache = MemoryCache<int>();
intCache.setByKey("age", 26);
print(intCache.getByKey("age")); // 26
// 3. 自定义对象缓存
final Cache<User> userCache = MemoryCache<User>();
userCache.setByKey("u1", User("Jack", 18));
final User? user = userCache.getByKey("u1");
print(user?.name); // Jack
// 删除测试
userCache.remove("u1");
print(userCache.getByKey("u1")); // null
}
// 自定义实体类
class User {
final String name;
final int age;
User(this.name, this.age);
}Code language: Dart (dart)

使用泛型集合字面量
List、Set、Map 字面量都支持参数化泛型:
- List / Set:在左方括号前写
<类型> - Map:在左大括号前写
<键类型, 值类型>
示例:
// 字符串列表
var names = <String>['Seth', 'Kathy', 'Lars'];
// 字符串集合(去重)
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
// 字符串键、字符串值的字典
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': '爬虫提示文件',
'humans.txt': '面向人类开发者的说明文件',
};
Code language: PHP (php)
运行例子
void main() {
// 1. List<String> 字符串列表:有序、允许重复、可按下标取值
var names = <String>['Seth', 'Kathy', 'Lars', 'Seth'];
print("=== List 列表 ===");
print(names); // [Seth, Kathy, Lars, Seth] 允许重复
print(names[0]); // 按下标取第一个:Seth
names.add("Mike"); // 末尾新增元素
print("新增后:$names");
// 2. Set<String> 字符串集合:无序、自动去重、无下标
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars', 'Seth'};
print("\n=== Set 集合(自动去重)===");
print(uniqueNames); // {Seth, Kathy, Lars} 重复的Seth自动删掉
print(uniqueNames.contains("Kathy")); // 判断是否存在:true
uniqueNames.remove("Lars");
print("删除Lars后:$uniqueNames");
// 3. Map<String, String> 键值对字典:键唯一,通过key取值
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': '爬虫提示文件',
'humans.txt': '面向人类开发者的说明文件',
};
print("\n=== Map 字典 ===");
print(pages);
print(pages['index.html']); // 通过key取值:Homepage
pages["about.html"] = "关于页面"; // 新增键值
print("新增about.html后:$pages");
// 常用遍历示例
print("\n=== 遍历List ===");
for (var name in names) {
print("- $name");
}
print("\n=== 遍历Map所有键值 ===");
for (var entry in pages.entries) {
print("文件:${entry.key} → 说明:${entry.value}");
}
}Code language: Dart (dart)
构造函数使用泛型参数
调用构造器时,在类名后方尖括号内指定泛型类型:
var nameSet = Set<String>.of(names);Code language: JavaScript (javascript)
下面代码创建一个键为 int、值为 View 类型的字典:
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("原始列表:$names");
print("去重Set:$nameSet");
// SplayTreeMap 需要 dart:collection
var views = SplayTreeMap<int, String>();
views[5] = "设置页面";
views[2] = "首页";
views[9] = "我的页面";
views[1] = "启动页";
print("\n有序TreeMap:");
for (var e in views.entries) {
print("${e.key} : ${e.value}");
}
}Code language: PHP (php)
泛型集合的运行时类型
类型实化 reified
Dart 的泛型是 实化(reified)的:运行时会完整保留泛型类型信息。
你可以在运行时直接判断集合的具体泛型类型:
void main() {
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // 输出 true
}Code language: PHP (php)
补充对比:Java 的泛型采用类型擦除,运行时会抹除泛型参数。Java 中只能判断对象是否是
List,无法区分List<String>和List<int>。
* 限制泛型参数类型
边界 bound,由于读者还没学到class,所以本部分内容了解就可以。
定义泛型时,可以限定传入的类型必须是某个父类的子类,这种约束称为类型边界,使用 extends 关键字实现。
1 限制非空类型
让泛型必须是非空类型(默认上限是可空 Object?):
class Foo<T extends Object> {
// 传入 T 的类型不能为可空类型
}
Code language: JavaScript (javascript)
2 限定为指定父类的子类
使用 extends 指定基类,这样你可以直接调用基类的所有成员方法:
class Foo<T extends SomeBaseClass> {
String toString() => "实例 Foo<$T>";
}
class Extender extends SomeBaseClass {}
Code language: JavaScript (javascript)
合法传参:父类本身、任意子类
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
Code language: HTML, XML (xml)
不传泛型参数时,默认使用边界类型:
var foo = Foo();
print(foo); // 输出:实例 Foo<SomeBaseClass>
Code language: PHP (php)
传入不相关类型会直接静态报错:
var foo = Foo<Object>(); // 编译报错:Object 不是 SomeBaseClass 子类
Code language: JavaScript (javascript)
* 自引用类型边界(F-bound 有界泛型)
约束泛型时,可以让边界引用自身类型参数,形成自约束,称为 F-bound。
标准示例:Comparable<T> 可比较接口
abstract interface class Comparable<T> {
int compareTo(T o);
}
// T 必须实现 Comparable<T>,只能和同类型对象比较
int compareAndOffset<T extends Comparable<T>>(T t1, T t2) =>
t1.compareTo(t2) + 1;
class A implements Comparable<A> {
@override
int compareTo(A other) {
// 实现比较逻辑
return 0;
}
}
int res = compareAndOffset(A(), A());
Code language: PHP (php)
约束 T extends Comparable<T> 的含义:T 只能和自身同类型实例做比较。
* 泛型方法
普通函数、方法同样支持泛型类型参数:
T first<T>(List<T> ts) {
// 前置校验逻辑
T tmp = ts[0];
// 后续处理逻辑
return tmp;
}
Code language: PHP (php)
看具体例子
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)
泛型参数 <T> 作用在三处:
- 函数返回值类型
T - 入参类型
List<T> - 局部变量类型
T tmp