一、什么是泛型
泛型,顾名思义,是指泛类型
,也就是说类型可以延迟到使用时候再决定,而非声明时决定。如同List<E>
这种写法,<E>
就声明了list
是一个泛型
类型,通常情况下,建议使用一个字母来代表类型参数,如E
,T
,S
等。
虽然Dart
中的类型是可选的,我们也可以选择不使用类型。但是如果希望清晰地表明预期类型
时,则可以传入具体的类型参数
,所以其实可以把泛型
看做是类型
的变量,而为泛型类型
指定具体类型
时,就好比为变量赋值,示例如下:
var names = new List<String>();
names.addAll(['Tony', 'Strange']);
names.add(123); // 检查模式下会报错
通过使用泛型,可以减少重复的代码,特别是对于那种 实现一致,仅有类型不同 的场景,如:需要一个保存缓存对象的接口,我们可以写成:
abstract class ObjectCache {
Object getByKey(String key);
setByKey(String key, Object value);
}
后来,我们发现需要实现一个缓存字符串
的接口,那么又定义了:
abstract class StringCache {
String getByKey(String key);
setByKey(String key, String value);
}
可以发现,他们的实现是一致的,唯一的区别是类型
,那么,我们可不可以把类型当做是一个变量呢?显然是可以的,而这也正是泛型
的功能:
abstract Cache<T> {
T getByKey(Stirng key);
setByKey(String key, T value);
}
使用时,我们就可以指定T
的具体类型了,如:
Cache<Object> objectCache;
Cache<String> stringCache;
二、使用集合字面量
List
和map
字面量也是可以参数化的:
- 参数化定义list,要在字面量前添加
<type>
- 参数化定义map,要在字面量前添加
<keyType, valueType>
通过参数化定义
,可以带来更安全的类型检查,并且可使用变量的自动类型推导,如下:
var name = <String>['Tony', 'Strange'];
var pages = <String, String>{
'index.html': 'HOME',
'robots.txt': 'Hints for web robots'
};
三、构造函数中的泛型
在调用构造函数时,可以在类名后使用<...>
来指定具体类型,如:
var names = new List<String>();
names.addAll(['Tony', 'Strange']);
var nameSet = new Set<String>.from(names);
四、判断泛型对象的类型
可以使用is
表达式来判断泛型对象
的类型,如:
var names = new List<String>();
print(names is List<String>); // true
但是需要注意的是:
生产模式
下不会进行类型检查,所以List<String>
可能包含非String
对象,这种情况下,建议是分别判断每个对象的类型或者处理类型转化异常
五、限制泛型类型
有时候,我们希望泛型不那么泛
,也就是说,希望泛型的可选类型是限制的,那么可以使用extends
关键字实现:
class A {}
class B extends A {}
class C {}
class SomeClass<T extends A>{
// ...
}
main() {
// 这种情况下是可以的,因为传入的类型符合限定(自身或者子类)
var a = new SomeClass<A>();
var b = new SomeClass<B>();
// 不显式指定泛型类型,也是可以的
var c = new SomeClass();
// 这种情况下不行,因为不符合限定
var d = new SomeClass<C>();
}
六、在函数中使用泛型
从Dart 1.21
开始,在函数中,也是可以使用泛型的,函数中泛型可以运用于:
- 函数的返回值类型
- 函数的参数类型
- 函数的局部变量类型
如下:
T foo<T>(List<T> ts) {
T tmp ?= ts[0];
return tmp;
}