Skip to content

泛型

泛型与类型安全

Dart 中变量的类型是可选的,所以在类型约束方面比较松散。但在某些场合中,我们仍然需要对类型进行约束,这时就要用到泛型了。
举个例子,Dart 中的 List 数据类型就支持泛型,因此默认 List 实例中可以放入任意类型的对象。示例代码如下:

1
2
3
4
5
List list = new List();
list.add(123);
list.add(true);
list.add({'123': 123});
print(list); // 输出: [123, true, {123: 123}]

在这个例子中,list 里包含整数类型、布尔类型和字典类型的数据。
但这样对这个 list 的使用者并不友好,因为使用者没有办法安全舒适地对 list 进行遍历。
更最重要的是,不现在可以放入容器中的数据类型可能会给维护工作带来巨大的灾难。因此,泛型是保证类型安全的重要途径。

在定义中使用泛型

当类中某个成员是类型不确定的数据时,可以在定义这个类的时候使用泛型代替这个成员的类型。下面我们举一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class TypeList<T> {
    List list = new List();

    add(T value) {
        list.add(value)
    }

    T operator [](int i) {
        return list[i];
    }

    forEach(f) => list.forEach(f);
}

void main() {
    TypeList<String> books = new TypeList<String>();
    books.add('《有趣的 Flutter》');
    books.add('《Flutter 入门经典》');
    books.forEach((s) => print(s));
}

这个例子中,我们使用 T 来表示泛型。在调用 TypeList 的时候,需要为其指定泛型的具体类型。
这里我们使用 TypeList<String> 将泛型 T 指定为 String 类型。于是在后面的操作中,我们只能向 books 中插入 String 类型的对象,否则编译器将报错

在函数中使用泛型

我们不仅可以在类中使用泛型,也可以在函数中使用。下面举一个例子:

1
2
3
4
T first<T> (List<T> ts) {
    T tmp = ts[0];
    return tmp;
}

这个例子中的函数返回值、List 中的泛型,以及临时变量 tmp 的类型都是 T。
在调用 first 函数时,我们需要在其后使用 <> 指定泛型的类型。示例代码如下,这里同样指定泛型 T 为 String 类型:

1
2
String str = first<String>(["123", "456"]);
print(str); // 123

限定泛型的类型

泛型并不意味着任意类型。在某些情况下,我们只希望泛型中的类型是某些指定的类型,此时可以限制泛型的类型。
举个例子,我们希望泛型 T 只能是 BaseClass 或其子类,此时可以用以下方式声明:

1
2
3
4
5
6
class TypeList<T extends BaseClass> {
    // 在这里写其他的实现
    String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass { }

此时若使用其他的类型,编译器将会报错:

1
2
3
4
Foo<BaseClass> someBaseClassFoo = Foo<BaseClass>();
Foo<Extender> extenderFoo = Foo<Extender>();

Foo<Object> foo = Foo<Object>(); // 编译错误