如果您查看基本数组类型 List 的 API 文档,您会发现该类型实际上是 List
为什么要使用泛型?
类型安全通常需要泛型,但它们比仅仅允许代码运行有更多好处:
- 正确指定泛型类型可以生成更好的代码。
- 您可以使用泛型来减少代码重复。
如果您打算让列表仅包含字符串,则可以将其声明为 List
×static analysis:erro/warning
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 是替代类型。它是一个占位符,您可以将其视为开发人员稍后定义的类型。
使用集合文字
列表、集合和映射文字可以参数化。参数化文字就像您已经见过的文字一样,只不过您在左括号之前添加了
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
相比之下,Java 中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。在Java中,你可以测试一个对象是否是一个List,但你不能测试它是否是一个 List
。
限制参数化类型
实现泛型类型时,您可能希望限制可以作为参数提供的类型,以便参数必须是特定类型的子类型。您可以使用 extends 来完成此操作。
一个常见的用例是通过使其成为 Object 的子类型(而不是默认的 Object? )来确保类型不可为 null。
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 )。
- 在参数的类型中 ( List
)。 - 局部变量的类型 ( T tmp )。