Dart 泛型与类型体操
Site Owner
Published on 2026-05-22
详解Dart泛型约束、协变/逆变、具化泛型与Java类型擦除对比、typedef与函数类型声明

Dart 泛型与类型体操
核心摘要:Dart 的泛型是具化泛型(Reified Generics)——类型信息在运行时完整保留,这与 Java 的类型擦除(Type Erasure)有根本差异。本文深入探讨 Dart 泛型的协变/逆变、上界约束、泛型函数、
Type对象,以及利用泛型构建类型安全的框架代码。重点对比 Java 泛型的局限性和 TypeScript 的条件类型。
目录
- 具化泛型 vs 类型擦除
- 泛型约束与上界
- 协变、逆变与不变
- 泛型函数与类型推断
- 运行时类型反射
- 类型安全的容器设计
- 与 Java / TypeScript / Kotlin 的对比
- 实战案例:类型安全的依赖注入容器
- 深度追问
- 总结表格
1. 具化泛型 vs 类型擦除
Java 的类型擦除(Type Erasure)
Java 泛型在编译后类型参数被抹掉,运行时无法知道泛型的具体类型:
// Java
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
// 运行时,两者都是 List(原始类型),类型参数被擦除
System.out.println(strings.getClass() == integers.getClass()); // true!
// 无法在运行时判断泛型类型
public <T> void process(List<T> list) {
// ❌ 编译错误:cannot use instanceof with generic type T
// if (list instanceof List<String>) { }
// ❌ 无法创建泛型数组
// T[] array = new T[10];
}
Dart 的具化泛型(Reified Generics)
Dart 在运行时完整保留类型信息:
void main() {
var strings = <String>[];
var integers = <int>[];
// 运行时可以区分泛型类型!
print(strings.runtimeType); // List<String>
print(integers.runtimeType); // List<int>
print(strings is List<String>); // true
print(strings is List<int>); // false ← 运行时检查!
// 可以比较类型
print(strings.runtimeType == integers.runtimeType); // false
}
// 泛型类型在运行时的意义
void checkType<T>(T value) {
if (T == String) {
print('这是一个字符串: $value');
} else if (T == int) {
print('这是一个整数: $value');
}
// 或者用 is
if (value is String) {
print('字符串长度: ${value.length}');
}
}
// 获取泛型类型对象
Type getType<T>() => T;
void main2() {
Type stringType = getType<String>();
Type intType = getType<int>();
print(stringType); // String
print(stringType == String); // true
}
2. 泛型约束与上界
2.1 单上界约束(extends)
// T 必须是 num 或其子类(int、double)
T max<T extends num>(T a, T b) => a > b ? a : b;
void main() {
print(max(3, 5)); // 5(int)
print(max(3.14, 2.72)); // 3.14(double)
// max('a', 'b'); // ❌ 编译错误:String 不是 num 的子类
}
// 泛型类的约束
class SortedList<T extends Comparable<T>> {
final List<T> _items = [];
void add(T item) {
_items.add(item);
_items.sort(); // 可以调用 sort,因为 T 实现了 Comparable
}
T get max => _items.last;
T get min => _items.first;
List<T> get items => List.unmodifiable(_items);
}
// 使用
final numbers = SortedList<int>()..add(3)..add(1)..add(2);
print(numbers.items); // [1, 2, 3]
print(numbers.max); // 3
// ❌ 编译错误:Object 没有实现 Comparable
// final objects = SortedList<Object>();
2.2 多约束(通过 Mixin 实现)
// Dart 不支持多上界(如 Java 的 T extends A & B),但可以通过接口组合解决
abstract interface class Saveable {
Future<void> save();
}
abstract interface class Validatable {
bool validate();
}
// 组合接口
abstract interface class Entity implements Saveable, Validatable {
int get id;
}
// T 必须实现 Entity(因此也实现了 Saveable 和 Validatable)
Future<void> saveIfValid<T extends Entity>(T entity) async {
if (entity.validate()) {
await entity.save();
print('Entity ${entity.id} 已保存');
} else {
print('Entity ${entity.id} 验证失败,跳过保存');
}
}
3. 协变、逆变与不变
3.1 Dart 的协变规则
// ⚠️ Dart 的 List 是协变的(Covariant),但这会导致运行时错误
void covariantTrap() {
List<int> ints = [1, 2, 3];
List<num> nums = ints; // ✅ 编译通过(List<int> 是 List<num> 的子类型)
// ❌ 运行时错误!ints 实际上是 List<int>,不能添加 double
nums.add(3.14); // throws: TypeError: 3.14 is not a subtype of int
}
// 安全的协变:只读集合
List<num> safeCovariant(List<int> ints) {
// List<int> 赋值给 List<num> 是安全的,前提是只读
return List<num>.unmodifiable(ints);
}
3.2 covariant 关键字
class Animal {
void eat(Animal food) {
print('${runtimeType} 吃了 ${food.runtimeType}');
}
}
class Cat extends Animal {
// covariant:将参数类型窄化为子类型
// 这违反了 Liskov 替换原则,但 Dart 允许并在运行时检查
@override
void eat(covariant Cat food) { // Cat.eat 只接受 Cat,不接受其他 Animal
print('猫咪 ${food.runtimeType}');
}
}
void main() {
Animal a = Cat();
a.eat(Cat()); // ✅ 运行时通过
// a.eat(Dog()); // ❌ 运行时 TypeError(虽然编译通过)
}
3.3 函数类型的逆变
// 函数类型中:参数类型逆变,返回类型协变
// void Function(Cat) 是 void Function(Animal) 的子类型?
// 不是!恰恰相反:void Function(Animal) 是 void Function(Cat) 的子类型
typedef AnimalHandler = void Function(Animal);
typedef CatHandler = void Function(Cat);
void processAnimals(List<Animal> animals, AnimalHandler handler) {
animals.forEach(handler);
}
// CatHandler 不能用在期望 AnimalHandler 的地方
// 因为 processAnimals 可能传入 Dog,而 CatHandler 只能处理 Cat
4. 泛型函数与类型推断
4.1 泛型函数
// 泛型函数
T identity<T>(T value) => value;
// 类型参数自动推断
var s = identity('hello'); // T 推断为 String
var n = identity(42); // T 推断为 int
// 多类型参数
MapEntry<K, V> pair<K, V>(K key, V value) => MapEntry(key, value);
// 泛型函数作为高阶函数
List<R> mapList<T, R>(List<T> list, R Function(T) transform) {
return list.map(transform).toList();
}
var lengths = mapList(['hello', 'world'], (s) => s.length); // List<int>
4.2 类型推断的边界
// Dart 的类型推断是局部的(Local Type Inference)
// ✅ 可以推断
var result = [1, 2, 3].map((x) => x * 2).toList(); // List<int>
// ⚠️ 推断失败时需要显式标注
var mixed = []; // List<dynamic>!
var typed = <int>[]; // ✅ 明确类型
// 泛型方法调用时的类型参数传递
// ✅ 可以推断:
final list = List.generate(5, (i) => i.toString()); // List<String>
// ⚠️ 某些情况必须显式传递类型参数
abstract class JsonParser<T> {
T parse(Map<String, dynamic> json);
}
T parseJson<T>(Map<String, dynamic> json, JsonParser<T> parser) {
return parser.parse(json);
}
// 调用时如果无法推断,需要显式指定
// final user = parseJson(json, UserParser()); // 推断为 User
4.3 泛型的运行时行为
// 泛型类的工厂构造函数
abstract class Serializable {
Map<String, dynamic> toJson();
}
class TypedList<T extends Serializable> {
final List<T> _items;
TypedList(this._items);
// 运行时获取元素类型
Type get elementType => T;
List<Map<String, dynamic>> serialize() {
return _items.map((item) => item.toJson()).toList();
}
// 用泛型创建特定类型的空列表
TypedList<T> empty() => TypedList<T>([]);
}
5. 运行时类型反射
// Type 对象
void reflectType<T>() {
final type = T; // 获取 Type 对象
print('类型名: $type');
print('是否为 String: ${type == String}');
print('是否为 int: ${type == int}');
}
// 工厂注册表(Registry Pattern)
class Registry<T> {
final Map<Type, T Function()> _factories = {};
void register<S extends T>(S Function() factory) {
_factories[S] = factory;
}
T? create<S extends T>() {
final factory = _factories[S];
return factory?.call();
}
bool hasType<S>() => _factories.containsKey(S);
}
// 使用
void main() {
final registry = Registry<Widget>();
registry.register<Button>(() => Button());
registry.register<TextField>(() => TextField());
final btn = registry.create<Button>(); // 返回 Button 实例
print(registry.hasType<Image>()); // false
}
// 类型安全的事件总线
class TypedEventBus {
final Map<Type, List<Function>> _handlers = {};
void on<T>(void Function(T event) handler) {
_handlers.putIfAbsent(T, () => []).add(handler);
}
void emit<T>(T event) {
final handlers = _handlers[T];
if (handlers != null) {
for (final handler in handlers) {
(handler as void Function(T))(event);
}
}
}
void off<T>(void Function(T event) handler) {
_handlers[T]?.remove(handler);
}
}
// 使用事件总线
class UserLoggedInEvent {
final String username;
UserLoggedInEvent(this.username);
}
void setupEventHandlers(TypedEventBus bus) {
bus.on<UserLoggedInEvent>((event) {
print('用户登录: ${event.username}');
});
bus.emit(UserLoggedInEvent('Alice')); // 触发事件
}
6. 类型安全的容器设计
// Result<T, E>:类型安全的错误处理
sealed class Result<T, E extends Exception> {
const Result();
bool get isSuccess => this is Ok<T, E>;
bool get isFailure => this is Err<T, E>;
T get valueOrThrow => switch (this) {
Ok(:final value) => value,
Err(:final error) => throw error,
};
T valueOr(T defaultValue) => switch (this) {
Ok(:final value) => value,
Err() => defaultValue,
};
Result<R, E> map<R>(R Function(T) transform) => switch (this) {
Ok(:final value) => Ok(transform(value)),
Err(:final error) => Err(error),
};
Result<R, E> flatMap<R>(Result<R, E> Function(T) transform) => switch (this) {
Ok(:final value) => transform(value),
Err(:final error) => Err(error),
};
void when({
required void Function(T value) ok,
required void Function(E error) err,
}) {
switch (this) {
case Ok(:final value): ok(value);
case Err(:final error): err(error);
}
}
}
final class Ok<T, E extends Exception> extends Result<T, E> {
final T value;
const Ok(this.value);
@override
String toString() => 'Ok($value)';
}
final class Err<T, E extends Exception> extends Result<T, E> {
final E error;
const Err(this.error);
@override
String toString() => 'Err($error)';
}
// 使用示例
Future<Result<User, NetworkException>> fetchUser(int id) async {
try {
final data = await api.get('/users/$id');
return Ok(User.fromJson(data));
} on NetworkException catch (e) {
return Err(e);
}
}
void handleUser(int id) async {
final result = await fetchUser(id);
result.when(
ok: (user) => print('欢迎 ${user.name}'),
err: (e) => print('错误: $e'),
);
// 链式操作
final greeting = (await fetchUser(id))
.map((user) => user.name)
.map((name) => '欢迎 $name')
.valueOr('获取失败');
}
7. 与 Java / TypeScript / Kotlin 的对比
Java 泛型的局限
// Java:无法在运行时获取泛型类型
public class Container<T> {
private T value;
// ❌ 编译错误:无法创建泛型数组
// private T[] array = new T[10];
// ❌ 编译错误:不能用 instanceof 检查泛型
// if (value instanceof T) { }
// ❌ 无法直接获取 T 的 Class 对象
// Class<T> type = T.class; // 错误!
// 绕过方案:传入 Class<T>("类型令牌")
public static <T> Container<T> of(Class<T> type, Object raw) {
return new Container<>(type.cast(raw));
}
}
TypeScript 的条件类型
// TypeScript 支持条件类型(Dart 没有等价特性)
type NonNullable<T> = T extends null | undefined ? never : T;
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// TypeScript 的 infer(Dart 无等价)
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type UnwrappedString = Unwrap<Promise<string>>; // string
Kotlin 泛型
// Kotlin:inline reified 泛型(类似 Dart,但更强大)
inline fun <reified T> isType(value: Any): Boolean {
return value is T // ✅ 运行时可以检查!(因为 reified)
}
// Kotlin:out/in(协变/逆变标注)
class Producer<out T>(private val value: T) {
fun get(): T = value
}
class Consumer<in T> {
fun accept(value: T) { println(value) }
}
val producer: Producer<Number> = Producer<Int>(42) // ✅ 协变
8. 实战案例:类型安全的依赖注入容器
// 简化的类型安全依赖注入容器
class Container {
final Map<_ServiceKey, dynamic> _singletons = {};
final Map<_ServiceKey, dynamic Function(Container)> _factories = {};
// 注册单例
void singleton<T>(T instance, {String? name}) {
_singletons[_ServiceKey(T, name)] = instance;
}
// 注册工厂(每次创建新实例)
void factory<T>(T Function(Container c) builder, {String? name}) {
_factories[_ServiceKey(T, name)] = builder;
}
// 注册懒加载单例
void lazySingleton<T>(T Function(Container c) builder, {String? name}) {
final key = _ServiceKey(T, name);
_factories[key] = (c) {
if (!_singletons.containsKey(key)) {
_singletons[key] = builder(c);
}
return _singletons[key];
};
}
// 解析依赖
T resolve<T>({String? name}) {
final key = _ServiceKey(T, name);
// 先查单例
if (_singletons.containsKey(key)) {
return _singletons[key] as T;
}
// 再查工厂
final factory = _factories[key];
if (factory != null) {
return factory(this) as T;
}
throw StateError('没有注册类型 $T${name != null ? ' (name: $name)' : ''}');
}
T call<T>({String? name}) => resolve<T>(name: name);
}
class _ServiceKey {
final Type type;
final String? name;
_ServiceKey(this.type, this.name);
@override
bool operator ==(Object other) =>
other is _ServiceKey && type == other.type && name == other.name;
@override
int get hashCode => Object.hash(type, name);
}
// 使用示例
void setupContainer() {
final container = Container();
// 注册基础设施
container.singleton<Database>(Database('postgresql://localhost/mydb'));
container.singleton<HttpClient>(HttpClient(baseUrl: 'https://api.example.com'));
// 注册仓库层(懒加载)
container.lazySingleton<UserRepository>(
(c) => UserRepository(c<Database>()),
);
container.lazySingleton<ProductRepository>(
(c) => ProductRepository(c<Database>(), c<HttpClient>()),
);
// 注册服务层
container.lazySingleton<UserService>(
(c) => UserService(c<UserRepository>()),
);
// 工厂:每次请求创建新的 RequestContext
container.factory<RequestContext>(
(c) => RequestContext(c<UserService>()),
);
// 使用
final userService = container<UserService>();
final ctx1 = container<RequestContext>(); // 新实例
final ctx2 = container<RequestContext>(); // 另一个新实例
print(identical(ctx1, ctx2)); // false(工厂)
print(identical(
container<UserService>(),
container<UserService>(),
)); // true(懒加载单例)
}
9. 深度追问
Q1:Dart 泛型是协变的,会不会导致安全问题?
会。Dart 的
List<T>是协变的(List<int>可赋值给List<num>),这在类型理论上是不安全的(因为List是可变的,可写入父类型的值)。Dart 通过运行时检查来补偿:当你向nums.add(3.14)时(而nums实际是List<int>),会抛出TypeError。这是设计权衡:更宽松的编译时规则 + 运行时安全网。
Q2:什么是具化泛型的代价?
具化泛型需要在运行时维护类型信息,增加了二进制大小和内存开销。Java 选择类型擦除的原因之一就是向后兼容和性能。但在 Dart/Flutter 中,AOT 编译会优化大部分类型检查,实际影响可忽略不计。
Q3:如何实现 TypeScript 中 Partial<T> 这样的工具类型?
Dart 目前没有条件类型和映射类型,无法用类型体操实现
Partial<T>。替代方案是:1)代码生成(build_runner+json_serializable)2)copyWith模式(手写)3)freezed包(自动生成 copyWith/fromJson/toJson)。这是 Dart 类型系统的局限,也是代码生成工具蓬勃发展的原因。
10. 总结表格
| 特性 | Dart | Java | TypeScript | Kotlin |
|---|---|---|---|---|
| 泛型运行时保留 | ✅ 完整保留 | ❌ 类型擦除 | ❌ 类型擦除 | ✅ reified inline 函数 |
| 运行时 is 检查 | ✅ list is List<int> | ❌ 只能 instanceof List | ❌ 编译时 | ✅ reified |
| 协变 | 隐式协变(运行时检查) | 通配符 ? extends | 结构子类型 | out 标注 |
| 逆变 | covariant 参数 | 通配符 ? super | 结构子类型 | in 标注 |
| 泛型上界 | T extends Type | T extends Type | T extends Type | T : Type |
| 多上界 | ❌ 间接通过接口 | ✅ T extends A & B | ✅ T extends A & B | ✅ where T : A, B |
| 条件类型 | ❌ | ❌ | ✅ T extends U ? X : Y | ❌ |
参考资料: