Dart速来系列
- Dart速来系列1,基础:变量、常量、数值、布尔、字符串、日期时间
- Dart速来系列2,集合:列表List,集合Map,集合Set,枚举enum
- Dart速来系列2-1 关于类你所应该知道的大多数高级特性
- Dart速来系列3,函数,function,闭包高阶匿名呀
- Dart速来系列4,那些关于操作符(expr),流程控制(flow),错误(err)的事
- Dart速来系列5,进阶:泛型,泛型,泛型,特斯拉和阿马
- Dart速来系列6,进阶:异步。把线程、Isolate、microtask、Event Queue简单说明白。
- Dart速来系列7,进阶:生成器Generator,类型定义typedef,空安全
- Dart速来系列8,进阶:拓展Extension。无需修改源码,为类修改拓展新功能。
什么是泛型?
简单的泛型:特斯拉和宝马,都是汽车!
当然如果你喜欢严谨一些,那么:
泛型能够帮助我们编写更加通用和安全的代码,并在运行时进行类型检查,减少潜在的错误。
泛型是一种编程语言的特性,它允许我们在定义函数、类或接口时使用类型参数。通过使用类型参数,我们可以编写更通用的代码,使其适用于多种类型,而不仅仅限于某个具体类型。
泛型函数
在Dart中,我们可以定义泛型函数。下面是一个简单的例子,展示了如何编写一个泛型的函数来交换两个元素的位置:
T swap<T>(T a, T b) {
T temp = a;
a = b;
b = temp;
return a;
}
void main() {
int a = 1;
int b = 2;
print('交换前:a = $a, b = $b');
int result = swap<int>(a, b);
print('交换后:a = $a, b = $b');
}
上述代码中的swap函数使用了类型参数T,它表示一个占位符类型。在调用该函数时,我们需要显式地指定类型参数的具体类型,如swap<int>(a, b)。通过泛型函数,我们可以在不同类型间轻松进行交换操作,提高了代码的复用性和灵活性。
泛型类
除了函数,Dart还支持泛型类的定义。泛型类可以是普通的类、抽象类或接口。为了更好地理解泛型类,我们将以一个汽车管理系统为例进行说明。
class Car<T> {
String brand;
T model;
Car(this.brand, this.model);
void drive() {
print('$brand ${model.toString()} 开始行驶!');
}
}
void main() {
// 创建一个使用字符串型号的汽车对象
Car<String> car1 = Car<String>('BMW', 'X5');
car1.drive(); // 输出:BMW X5 开始行驶!
// 创建一个使用整型型号的汽车对象
Car<int> car2 = Car<int>('Tesla', 2023);
car2.drive(); // 输出:Tesla 2023 开始行驶!
}
在上述代码中,我们定义了一个泛型类Car,它有两个属性:brand表示汽车品牌,model表示汽车型号。通过使用类型参数T,我们可以为model属性指定不同的类型。在main函数中,我们创建了两个不同类型的汽车对象,并调用了drive方法来启动汽车。
限定类型参数
有时,我们希望对类型参数进行限制,以确保其满足特定条件。Dart提供了限定类型参数的功能,我们可以使用extends关键字来限定类型参数必须是某个类的子类或实现了某个接口。为了更好地理解,我们将继续沿用汽车管理系统的例子。
class Car<T extends Engine> {
String brand;
T engine;
Car(this.brand, this.engine);
void drive() {
engine.start();
print('$brand 开始行驶!');
}
}
abstract class Engine {
void start();
}
class GasEngine implements Engine {
@override
void start() {
print('点火启动汽车引擎!');
}
}
class ElectricEngine implements Engine {
@override
void start() {
print('启动电动汽车引擎!');
}
}
void main() {
// 创建一个使用燃油引擎的汽车对象
Car<GasEngine> car1 = Car<GasEngine>('BMW', GasEngine());
car1.drive(); // 输出:点火启动汽车引擎!BMW 开始行驶!
// 创建一个使用电动引擎的汽车对象
Car<ElectricEngine> car2 = Car<ElectricEngine>('Tesla', ElectricEngine());
car2.drive(); // 输出:启动电动汽车引擎!Tesla 开始行驶!
}
在上述代码中,我们对泛型类Car的类型参数T进行了限定,要求它必须是Engine类的子类或实现了Engine接口。通过限定类型参数,我们可以确保在创建Car对象时,所传递的引擎类型满足特定的要求。在main函数中,我们创建了两个不同类型的汽车对象,并成功地启动了汽车。
泛型接口
在Dart中,我们还可以定义泛型接口。泛型接口可以在定义类时指定具体的类型参数。为了更好地理解,我们将以一个数据存储库为例进行说明。
abstract class Repository<T> {
void save(T item);
T getById(int id);
}
class UserRepository implements Repository<User> {
List<User> _users = [];
@override
void save(User user) {
_users.add(user);
}
@override
User getById(int id) {
return _users.firstWhere((user) => user.id == id, orElse: () => null);
}
}
class User {
int id;
String name;
User(this.id, this.name);
}
void main() {
UserRepository userRepository = UserRepository();
User user = User(1, 'John');
userRepository.save(user);
User savedUser = userRepository.getById(1);
print(savedUser?.name); // 输出:John
}
在上述代码中,我们定义了一个泛型接口Repository,用于实现数据存储的功能。通过使用类型参数T,我们可以在实现Repository接口时指定具体的存储数据类型。在UserRepository类中,我们实现了Repository<User>接口,并对用户数据进行存储和查询操作。在main函数中,我们创建了一个UserRepository对象,保存了一个用户对象,并成功地通过用户id获取到了该用户。