愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

Dart学习笔记(六):泛型

一、什么是泛型

泛型,顾名思义,是指泛类型,也就是说类型可以延迟到使用时候再决定,而非声明时决定。如同List<E>这种写法,<E>就声明了list是一个泛型类型,通常情况下,建议使用一个字母来代表类型参数,如ETS等。
虽然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;


二、使用集合字面量

Listmap字面量也是可以参数化的:

  • 参数化定义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;
}