Dart 泛型
泛型(Generics)让你在编写代码时不指定具体类型,而是用类型参数占位,在使用时再确定类型。
泛型的核心价值是类型安全和代码复用——一份代码适配多种类型,同时编译器帮你检查类型错误。
为什么需要泛型
先看一个没有泛型时的痛点。
实例
没有泛型的写法:
// 没有泛型:只能存放 Object,取出时需要手动转型
class IntBox {
int value;
IntBox(this.value);
}
class StringBox {
String value;
StringBox(this.value);
}
// 每新增一种类型就要写一个新的 Box 类,代码大量重复
void main() {
var intBox = IntBox(42);
var stringBox = StringBox('RUNOOB');
print('整数: ${intBox.value}');
print('字符串: ${stringBox.value}');
}
class IntBox {
int value;
IntBox(this.value);
}
class StringBox {
String value;
StringBox(this.value);
}
// 每新增一种类型就要写一个新的 Box 类,代码大量重复
void main() {
var intBox = IntBox(42);
var stringBox = StringBox('RUNOOB');
print('整数: ${intBox.value}');
print('字符串: ${stringBox.value}');
}
有没有办法写一个 Box 类,既能装 int、又能装 String,还能在编译时保证类型安全?
这就是泛型要解决的问题。
泛型类
泛型类在类名后用尖括号 <> 声明类型参数,类内部使用这个类型参数。
实例
// <T> 是类型参数,T 是一个占位符,代表"某种类型"
// 约定俗成的命名:T(Type)、E(Element)、K(Key)、V(Value)
class Box<T> {
T value;
Box(this.value);
T getValue() {
return value;
}
void setValue(T newValue) {
value = newValue;
}
void printValue() {
print('Box 中的值: $value (类型: ${value.runtimeType})');
}
}
void main() {
// 使用泛型类时指定具体类型
var intBox = Box<int>(42);
var stringBox = Box<String>('RUNOOB Dart 教程');
var doubleBox = Box<double>(3.14);
intBox.printValue();
stringBox.printValue();
doubleBox.printValue();
// 类型安全:下面的代码会在编译时报错
// intBox.setValue('hello'); // 错误:String 不能赋值给 int
// 类型推断:Dart 可以根据构造函数参数推断类型
var autoBox = Box('自动推断为 String');
print('推断类型: ${autoBox.value.runtimeType}');
}
// 约定俗成的命名:T(Type)、E(Element)、K(Key)、V(Value)
class Box<T> {
T value;
Box(this.value);
T getValue() {
return value;
}
void setValue(T newValue) {
value = newValue;
}
void printValue() {
print('Box 中的值: $value (类型: ${value.runtimeType})');
}
}
void main() {
// 使用泛型类时指定具体类型
var intBox = Box<int>(42);
var stringBox = Box<String>('RUNOOB Dart 教程');
var doubleBox = Box<double>(3.14);
intBox.printValue();
stringBox.printValue();
doubleBox.printValue();
// 类型安全:下面的代码会在编译时报错
// intBox.setValue('hello'); // 错误:String 不能赋值给 int
// 类型推断:Dart 可以根据构造函数参数推断类型
var autoBox = Box('自动推断为 String');
print('推断类型: ${autoBox.value.runtimeType}');
}
Box 中的值: 42 (类型: int) Box 中的值: RUNOOB Dart 教程 (类型: String) Box 中的值: 3.14 (类型: double) 推断类型: String
多个类型参数
泛型类可以有多个类型参数。
实例
// K 表示键的类型,V 表示值的类型
class Pair<K, V> {
K key;
V value;
Pair(this.key, this.value);
@override
String toString() => 'Pair($key: $value)';
}
void main() {
var pair1 = Pair<String, int>('runoob', 10);
var pair2 = Pair('score', 95.5); // 类型推断
print(pair1);
print(pair2);
}
class Pair<K, V> {
K key;
V value;
Pair(this.key, this.value);
@override
String toString() => 'Pair($key: $value)';
}
void main() {
var pair1 = Pair<String, int>('runoob', 10);
var pair2 = Pair('score', 95.5); // 类型推断
print(pair1);
print(pair2);
}
Pair(runoob: 10) Pair(score: 95.5)
泛型方法
除了泛型类,函数和方法也可以使用泛型。
实例
// 泛型函数:返回类型和参数类型一致
// 在返回值类型前声明 <T>
T firstElement<T>(List<T> list) {
if (list.isEmpty) {
throw ArgumentError('列表不能为空');
}
return list[0];
}
// 泛型函数:交换两个值
void swap<T>(List<T> list, int i, int j) {
T temp = list[i];
list[i] = list[j];
list[j] = temp;
}
void main() {
// 调用时 Dart 会自动推断类型
var names = ['runoob', 'Dart', 'Flutter'];
print('第一个名字: ${firstElement(names)}');
var scores = [95, 88, 72];
print('第一个分数: ${firstElement(scores)}');
// 交换元素
var items = ['A', 'B', 'C'];
print('交换前: $items');
swap(items, 0, 2);
print('交换后: $items');
}
// 在返回值类型前声明 <T>
T firstElement<T>(List<T> list) {
if (list.isEmpty) {
throw ArgumentError('列表不能为空');
}
return list[0];
}
// 泛型函数:交换两个值
void swap<T>(List<T> list, int i, int j) {
T temp = list[i];
list[i] = list[j];
list[j] = temp;
}
void main() {
// 调用时 Dart 会自动推断类型
var names = ['runoob', 'Dart', 'Flutter'];
print('第一个名字: ${firstElement(names)}');
var scores = [95, 88, 72];
print('第一个分数: ${firstElement(scores)}');
// 交换元素
var items = ['A', 'B', 'C'];
print('交换前: $items');
swap(items, 0, 2);
print('交换后: $items');
}
第一个名字: runoob 第一个分数: 95 交换前: [A, B, C] 交换后: [C, B, A]
类型约束 extends
有时候你希望类型参数只能是某些类型的子类型。
使用 extends 关键字可以对泛型参数施加约束。
实例
// 定义一个数值计算器
// <T extends num> 约束 T 必须是 num 或其子类(int、double)
class Calculator<T extends num> {
T a;
T b;
Calculator(this.a, this.b);
// 因为约束了 T extends num,所以可以安全地使用 num 的方法
double add() => (a + b).toDouble();
double subtract() => (a - b).toDouble();
double multiply() => (a * b).toDouble();
double divide() => a / b;
void printOperations() {
print('RUNOOB 计算器: $a 和 $b');
print(' 加: ${add()}');
print(' 减: ${subtract()}');
print(' 乘: ${multiply()}');
print(' 除: ${divide().toStringAsFixed(2)}');
}
}
void main() {
var intCalc = Calculator<int>(10, 3);
intCalc.printOperations();
var doubleCalc = Calculator<double>(3.5, 1.5);
doubleCalc.printOperations();
// 下面这行会报错:String 不是 num 的子类
// var strCalc = Calculator<String>('a', 'b'); // 错误!
}
// <T extends num> 约束 T 必须是 num 或其子类(int、double)
class Calculator<T extends num> {
T a;
T b;
Calculator(this.a, this.b);
// 因为约束了 T extends num,所以可以安全地使用 num 的方法
double add() => (a + b).toDouble();
double subtract() => (a - b).toDouble();
double multiply() => (a * b).toDouble();
double divide() => a / b;
void printOperations() {
print('RUNOOB 计算器: $a 和 $b');
print(' 加: ${add()}');
print(' 减: ${subtract()}');
print(' 乘: ${multiply()}');
print(' 除: ${divide().toStringAsFixed(2)}');
}
}
void main() {
var intCalc = Calculator<int>(10, 3);
intCalc.printOperations();
var doubleCalc = Calculator<double>(3.5, 1.5);
doubleCalc.printOperations();
// 下面这行会报错:String 不是 num 的子类
// var strCalc = Calculator<String>('a', 'b'); // 错误!
}
RUNOOB 计算器: 10 和 3 加: 13.0 减: 7.0 乘: 30.0 除: 3.33 RUNOOB 计算器: 3.5 和 1.5 加: 5.0 减: 2.0 乘: 5.25 除: 2.33
类型约束让你既能保持泛型的灵活性,又能使用约束类型的方法。比如上面约束 T extends num,就可以在方法体内使用 +、-、*、/ 等运算符,因为 num 类型支持这些操作。
泛型集合的使用
Dart 的核心集合类(List、Set、Map)都是泛型的。
使用泛型集合可以获得编译期的类型检查。
实例
void main() {
// 指定元素类型为 String
List<String> names = ['runoob', 'Dart', 'Flutter'];
// names.add(42); // 错误:int 不能添加到 List<String>
// 指定键值类型
Map<String, int> scores = {
'runoob': 95,
'Alice': 87,
};
// scores['Bob'] = '优秀'; // 错误:String 不能赋值给 int
// 指定 Set 元素类型
Set<double> prices = {9.99, 19.99, 29.99};
// 泛型集合的函数式操作
List<int> numbers = [1, 2, 3, 4, 5, 6];
// 类型安全的过滤和映射
List<int> evenNumbers = numbers.where((n) => n % 2 == 0).toList();
List<String> labels = numbers.map((n) => 'RUNOOB-$n').toList();
print('偶数: $evenNumbers');
print('标签: $labels');
// 使用 fold 进行类型安全的累积计算
int sum = numbers.fold<int>(0, (prev, n) => prev + n);
print('总和: $sum');
}
// 指定元素类型为 String
List<String> names = ['runoob', 'Dart', 'Flutter'];
// names.add(42); // 错误:int 不能添加到 List<String>
// 指定键值类型
Map<String, int> scores = {
'runoob': 95,
'Alice': 87,
};
// scores['Bob'] = '优秀'; // 错误:String 不能赋值给 int
// 指定 Set 元素类型
Set<double> prices = {9.99, 19.99, 29.99};
// 泛型集合的函数式操作
List<int> numbers = [1, 2, 3, 4, 5, 6];
// 类型安全的过滤和映射
List<int> evenNumbers = numbers.where((n) => n % 2 == 0).toList();
List<String> labels = numbers.map((n) => 'RUNOOB-$n').toList();
print('偶数: $evenNumbers');
print('标签: $labels');
// 使用 fold 进行类型安全的累积计算
int sum = numbers.fold<int>(0, (prev, n) => prev + n);
print('总和: $sum');
}
偶数: [2, 4, 6] 标签: [RUNOOB-1, RUNOOB-2, RUNOOB-3, RUNOOB-4, RUNOOB-5, RUNOOB-6] 总和: 21
实际应用:泛型仓库模式
泛型在实际开发中最经典的应用之一是"仓库模式"(Repository Pattern)。
实例
// 定义一个通用仓库接口
abstract class Repository<T> {
void add(T item);
void remove(T item);
T? findById(String id);
List<T> getAll();
}
// 用户模型
class User {
String id;
String name;
User(this.id, this.name);
@override
String toString() => 'User($id, $name)';
}
// 商品模型
class Product {
String id;
String name;
double price;
Product(this.id, this.name, this.price);
@override
String toString() => 'Product($name, ¥$price)';
}
// 具体实现:用户仓库
class UserRepository implements Repository<User> {
final List<User> _users = [];
@override
void add(User user) => _users.add(user);
@override
void remove(User user) => _users.remove(user);
@override
User? findById(String id) {
try {
return _users.firstWhere((u) => u.id == id);
} catch (_) {
return null;
}
}
@override
List<User> getAll() => List.unmodifiable(_users);
}
// 具体实现:商品仓库(同样的接口,不同的类型)
class ProductRepository implements Repository<Product> {
final List<Product> _products = [];
@override
void add(Product product) => _products.add(product);
@override
void remove(Product product) => _products.remove(product);
@override
Product? findById(String id) {
try {
return _products.firstWhere((p) => p.id == id);
} catch (_) {
return null;
}
}
@override
List<Product> getAll() => List.unmodifiable(_products);
}
void main() {
var userRepo = UserRepository();
userRepo.add(User('u1', 'runoob'));
userRepo.add(User('u2', 'Alice'));
var productRepo = ProductRepository();
productRepo.add(Product('p1', 'Dart 教程', 29.99));
productRepo.add(Product('p2', 'Flutter 教程', 49.99));
print('用户列表: ${userRepo.getAll()}');
print('商品列表: ${productRepo.getAll()}');
print('查找用户 u1: ${userRepo.findById('u1')}');
print('查找商品 p2: ${productRepo.findById('p2')}');
}
abstract class Repository<T> {
void add(T item);
void remove(T item);
T? findById(String id);
List<T> getAll();
}
// 用户模型
class User {
String id;
String name;
User(this.id, this.name);
@override
String toString() => 'User($id, $name)';
}
// 商品模型
class Product {
String id;
String name;
double price;
Product(this.id, this.name, this.price);
@override
String toString() => 'Product($name, ¥$price)';
}
// 具体实现:用户仓库
class UserRepository implements Repository<User> {
final List<User> _users = [];
@override
void add(User user) => _users.add(user);
@override
void remove(User user) => _users.remove(user);
@override
User? findById(String id) {
try {
return _users.firstWhere((u) => u.id == id);
} catch (_) {
return null;
}
}
@override
List<User> getAll() => List.unmodifiable(_users);
}
// 具体实现:商品仓库(同样的接口,不同的类型)
class ProductRepository implements Repository<Product> {
final List<Product> _products = [];
@override
void add(Product product) => _products.add(product);
@override
void remove(Product product) => _products.remove(product);
@override
Product? findById(String id) {
try {
return _products.firstWhere((p) => p.id == id);
} catch (_) {
return null;
}
}
@override
List<Product> getAll() => List.unmodifiable(_products);
}
void main() {
var userRepo = UserRepository();
userRepo.add(User('u1', 'runoob'));
userRepo.add(User('u2', 'Alice'));
var productRepo = ProductRepository();
productRepo.add(Product('p1', 'Dart 教程', 29.99));
productRepo.add(Product('p2', 'Flutter 教程', 49.99));
print('用户列表: ${userRepo.getAll()}');
print('商品列表: ${productRepo.getAll()}');
print('查找用户 u1: ${userRepo.findById('u1')}');
print('查找商品 p2: ${productRepo.findById('p2')}');
}
用户列表: [User(u1, runoob), User(u2, Alice)] 商品列表: [Product(Dart 教程, ¥29.99), Product(Flutter 教程, ¥49.99)] 查找用户 u1: User(u1, runoob) 查找商品 p2: Product(Flutter 教程, ¥49.99)
泛型仓库模式让你用统一的接口处理不同类型的数据,既保证了类型安全,又避免了重复代码。
