为什么要有泛型
假如没有泛型
假设你写一个保存对象的容器类,如果不用泛型,你可能这样写:
class Box {
Object? content;
}
void main() {
var box = Box();
box.content = 123; // int
box.content = "Hello"; // String
// 读取时必须手动转换
String text = box.content as String;
}
问题:
- 不安全:任何类型都能存进去,可能取出来出错(
as
转型失败)。 - 代码冗余:不同类型需要反复写强制类型转换。
泛型的引入
有了泛型,我们可以这样写:
class Box<T> {
T? content;
}
void main() {
var intBox = Box<int>();
intBox.content = 123;
// intBox.content = "Hello"; // ❌ 编译期报错
var strBox = Box<String>();
strBox.content = "Hello";
}
<T>
是类型参数,T 在使用时被具体类型替换。- 编译器会在编译期检查类型,避免运行时错误。
泛型的优势
- 类型安全:编译期检查,避免运行时错误。
- 代码复用:不用为不同类型重复写逻辑。
- 性能优化:避免
Object
+ 强制类型转换的开销。
常见泛型用法
泛型与集合
Dart 的集合类本身就是泛型:
List<int> numbers = [1, 2, 3];
Map<String, double> prices = {"apple": 3.5, "banana": 2.0};
如果不用泛型:
List items = [1, "two", 3.0]; // 动态类型,取出来要强转
类的泛型
class Pair<K, V> {
K key;
V value;
Pair(this.key, this.value);
}
void main() {
var pair = Pair<String, int>("age", 25);
print("${pair.key}: ${pair.value}");
}
- 可以有多个泛型参数(
K
、V
)。
方法的泛型
泛型不仅能写在类上,也可以写在方法上:
T firstElement<T>(List<T> list) {
return list[0];
}
void main() {
print(firstElement<int>([1, 2, 3])); // 1
print(firstElement(["a", "b", "c"])); // 自动推断 T = String
}
限定泛型(泛型约束)
有时候你希望泛型必须是某个类的子类,这就需要 extends
:
class Animal {
void speak() => print("Animal sound");
}
class Dog extends Animal {
void speak() => print("Woof!");
}
void makeSpeak<T extends Animal>(T animal) {
animal.speak();
}
void main() {
makeSpeak(Dog()); // Woof!
// makeSpeak("Hello"); // ❌ 编译期错误
}
泛型的类型推断
在绝大多数情况下 Dart 能自动推断泛型类型:
class Box<T> {
T? content;
}
var box = Box("Hello"); // 推断为 Box<String>
但也可以显式指定:
class Box<T> {
T? content;
}
var box = Box<int>(123);
小心的坑
泛型在运行时会被类型擦除
Dart 运行时不保留泛型参数类型,类型检查发生在编译期。
例如:
print([1, 2, 3] is List<int>); // true print([1, 2, 3] is List<String>); // false
这里 Dart 做了特殊处理来保留类型信息,但底层并不是完整保留。
不能直接创建泛型类型的实例
class Box<T> { // var content = T(); // ❌ 错误,不能直接 new T }
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com