Dart 泛型深度解析:从类型擦除到 Reified 泛型,面试官想听什么?
Dart 泛型面试向解读:从类型擦除到 Reified 泛型(运行时保留类型),讲清泛型函数、泛型类与约束(extends)。 掌握后能答出 Dart 与普通 JVM 语言泛型的本质差异。
一句话概括
Dart 泛型在编译时保证类型安全,而且运行时保留类型信息(Reified)——这是它和 Java/TypeScript 最大的不同,也是 Flutter 中 List<int> is List<num> 能正确工作的根本原因。
核心知识点
1. 泛型类:类型安全的容器
1
2
3
4
5
6
7
8
class Box<T> {
T value;
Box(this.value);
}
final intBox = Box<int>(42);
// intBox.value = 'hello'; // ❌ 编译错误
final strBox = Box('hi'); // 类型推断 → Box<String>
面试要点:类型参数可以从构造函数实参自动推断,不用显式写 <String>。
2. 泛型约束:限制类型范围的 <T extends X>
1
2
3
4
5
6
7
8
9
10
11
12
// T 必须实现 Comparable,才能调用 compareTo
class SortedList<T extends Comparable<T>> {
final List<T> _items = [];
void add(T item) {
_items.add(item);
_items.sort((a, b) => a.compareTo(b));
}
}
final nums = SortedList<int>()..add(3)..add(1)..add(2);
print(nums.items); // [1, 2, 3]
// SortedList<Object>(); // ❌ Object 没有实现 Comparable
Dart 泛型约束只用 extends,不管后面是类还是接口。如果需要多约束,让具体类同时实现多个接口。
3. Reified 泛型:运行时类型不擦除
这就是 Dart 泛型的杀手锏:
1
2
3
4
5
6
7
8
9
10
11
final list = <int>[1, 2, 3];
print(list is List<int>); // true ✅ — Java 做不到
print(list is List<String>);// false ✅ — 运行时也知道类型不同
print(list.runtimeType); // List<int> — 不是擦除后的 List
// 可以直接用类型参数做分支
T fromJson<T>(Map<String, dynamic> json) {
if (T == User) return User.fromJson(json) as T;
if (T == Product) return Product.fromJson(json) as T;
throw ArgumentError('Unknown type: $T');
}
| 语言 | List<T> 运行时 | is List<int> |
|---|---|---|
| Dart | List<int> | ✅ 正确 |
| Java | ArrayList(擦除) | ❌ 编译错误 |
| TypeScript | 无运行时类型 | ❌ 不存在 |
4. 泛型方法:类型推断链路
1
2
3
4
5
T first<T>(List<T> items) => items.first;
final n = first([1, 2, 3]); // T = int,从参数推断
// 不是从返回值上下文推断的
List<String> x = first(['a', 'b']); // T = String,还是从参数推断
推断规则优先级:显式指定 > 参数类型 > 返回类型上下文。和 TypeScript 不一样,Dart 不会为了匹配赋值目标而改变泛型推断。
5. 协变带来的陷阱
Dart 的 List 是协变的:List<Cat> 可以赋值给 List<Animal>:
1
2
3
4
List<Cat> cats = [Cat()];
List<Animal> animals = cats; // ✅ 协变,编译通过
animals.add(Dog()); // ✅ 编译通过!运行时也不报错
// 现在 cats 里混进了 Dog 😱
解决方案:用 List.unmodifiable() 创建不可变列表,或者写的时候用具体类型。
其实你每天都在用
List<int>/Map<String, dynamic>:Dart 集合库全都是泛型,jsonDecode返回的Map<String, dynamic>就是典型Future<T>:网络请求返回Future<User>,类型安全地拿到 User 对象State<T extends StatefulWidget>:Flutter 中State<T>能安全访问widget属性,背后就是泛型约束Provider.of<T>(context):泛型让 Provider 知道你要取哪个类型的状态Freezed / json_serializable:代码生成依赖 Reified 泛型做 JSON 反序列化的类型保护
常见误解(FAQ)
❌ 误区:「Dart 泛型和 Java 泛型一样的」
不一样。Java 用类型擦除,运行时 List<String> 就是 List。Dart 是 Reified,运行时保留了 List<String> 的完整类型信息,is 检查能正确区分。
❌ 误区:「T extends A & B 是合法的泛型约束」
Dart 不支持 & 语法。只能用 extends 接单个约束,多约束时让具体类实现多个接口:
1
2
3
4
abstract class A {}
abstract class B {}
class C implements A, B {}
// 然后写 class Container<T extends C>
❌ 误区:「协变总是安全的」
Dart 的 List 协变是不安全的协变——List<Cat> 能赋值给 List<Animal>,之后往里面 add(Dog) 不会触发运行时错误(和 Java 数组的行为类似)。这不是 bug,是 Dart 刻意为之的设计取舍。
❌ 误区:「泛型方法可以靠返回值类型来推断类型参数」
不能。Dart 从参数推断,不是从赋值上下文推断。这和 TypeScript 的关键区别。
一句话总结
Dart 泛型 = 编译时类型安全 + 运行时类型不擦除(Reified),让你能写出更安全的代码,也能在面试里吊打不会的候选人。