泛型
如果你查看基本数组类型 List
的 API 文档,你会发现该类型实际上是 List<E>
。<...>
符号将 List 标记为泛型(或参数化)类型——一种具有形式类型参数的类型。 按照惯例,大多数类型变量都有单字母名称,例如 E、T、S、K 和 V。
为什么要使用泛型?
#泛型通常是类型安全所必需的,但它们的好处不仅仅是让你的代码运行
- 正确指定泛型类型可以生成更好的代码。
- 你可以使用泛型来减少代码重复。
如果你希望列表仅包含字符串,你可以将其声明为 List<String>
(读作“字符串列表”)。这样,你、你的同事程序员和你的工具就可以检测到将非字符串分配给列表可能是一个错误。这是一个例子
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
使用泛型的另一个原因是减少代码重复。泛型允许你在许多类型之间共享单个接口和实现,同时仍然利用静态分析。例如,假设你创建一个用于缓存对象的接口
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
你发现你想要一个此接口的字符串特定版本,因此你创建了另一个接口
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
稍后,你决定想要一个此接口的数字特定版本... 你明白了。
泛型类型可以让你免于创建所有这些接口的麻烦。相反,你可以创建一个采用类型参数的单个接口
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
在此代码中,T 是占位符类型。它是一个占位符,你可以将其视为开发人员稍后将定义的类型。
使用集合字面量
#列表、集合和映射字面量可以参数化。参数化字面量与你已经见过的字面量类似,只不过你在左括号前添加 <type>
(对于列表和集合)或 <keyType, valueType>
(对于映射)。这是一个使用类型化字面量的例子
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
使用带构造函数的参数化类型
#要在使用构造函数时指定一个或多个类型,请将这些类型放在类名称后的尖括号(<...>
)中。例如
var nameSet = Set<String>.from(names);
以下代码创建一个具有整数键和 View 类型值的映射
var views = Map<int, View>();
泛型集合及其包含的类型
#Dart 泛型类型是具体化的,这意味着它们在运行时携带其类型信息。例如,你可以测试集合的类型
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
限制参数化类型
#在实现泛型类型时,你可能希望限制可以作为参数提供的类型,以便该参数必须是特定类型的子类型。你可以使用 extends
来执行此操作。
一个常见的用例是通过使其成为 Object
的子类型(而不是默认的 Object?
)来确保类型不可为空。
class Foo<T extends Object> {
// Any type provided to Foo for T must be non-nullable.
}
你也可以将 extends
与 Object
以外的其他类型一起使用。这是一个扩展 SomeBaseClass
的示例,以便可以在 T
类型的对象上调用 SomeBaseClass
的成员
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
可以使用 SomeBaseClass
或其任何子类型作为泛型参数
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
也可以不指定泛型参数
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
指定任何非 SomeBaseClass
类型都会导致错误
var foo = Foo<Object>();
使用泛型方法
#方法和函数也允许类型参数
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}
这里,first
上的泛型类型参数 (<T>
) 允许你在多个位置使用类型参数 T
- 在函数的返回类型中 (
T
)。 - 在参数的类型中 (
List<T>
)。 - 在局部变量的类型中 (
T tmp
)。
除非另有说明,否则本网站上的文档反映了 Dart 3.6.0。页面上次更新时间为 2024-11-17。 查看源代码或报告问题。