Dart基础之泛型

9,070 阅读4分钟

前言

如果你查过基本数组类型 List 的API文档,会看到该类型实际上是List<E><...>表示法将List标记为通用(或参数化)类型 - 具有正式类型参数的类型。 按照惯例,大多数类型变量都有单字母名称,例如E,T,S,K和V.

为什么要用泛型

泛型的时候常常需要类型安全,但他们相比直接运行代码会有更多的好处:

  • 正确指定泛型类型会产生更好的生成代码。(对于写代码而言)
  • 你可以使用泛型来减少代码重复。

如果你希望List只包含String,则可以将其声明为List <String>(将其称为“字符串列表”)。 这样,你,你的伙伴和你的工具,可以明白将非字符串分配给列表可能是一个错误。 举个例子:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // 静态解析的时候就会提示错误

使用泛型的另一个原因是减少重复代码。 泛型允许你在 多种类型之间共享单个接口和实现,同时仍然利用静态分析。 例如,假设你创建了一个用于缓存对象的接口:

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是替身类型。 它是一个占位符,你可以将其视为开发人员稍后定义的类型。

使用集合文字

可以参数化List,Set 和 Map。 参数化文字就像你已经看到的文字一样,除了你在开始括号之前添加<type>(对于List和Set)或<keyType,valueType>(对于maps)。 以下是使用类型文字的示例:

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类型值的map:

var views = Map<int, View>();

通用集合及其包含的类型

Dart泛型类型被定义,这意味着它们在运行时携带它们的类型信息。 例如,你可以测试集合的类型:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

注意:相反,Java中的泛型使用擦除,这意味着在运行时删除泛型类型参数。 在Java中,你可以测试对象是否为List,但无法测试它是否为List<String>

限制参数化类型

实现泛型类型时,你可能希望限制其参数的类型。 你可以使用extends来执行此操作。

class Foo<T extends SomeBaseClass> {
  // 在这里实现
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

可以使用SomeBaseClass或其任何子类作为通用参数:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

也可以不指定泛型参数:

var foo = Foo();
print(foo); //'Foo<SomeBaseClass>'的实例 

指定任何非SomeBaseClass类型会导致错误:

var foo = Foo<Object>();//静态解析报错

使用泛型方法

最初,Dart的泛型支持仅限于classes。 一种称新的语法称为泛型方法,允许在方法和函数上使用类型参数:

T first<T>(List<T> ts) {
  // 做一些初始化工作或者检查错误...
  T tmp = ts[0];
  // 做一些额外的检查或者处理...
  return tmp;
}

这里,first(<T>)上的泛型类型参数允许你在多处使用类型参数T

  • 在函数的返回类型(T)中。
  • 在参数的类型(List<T>)中。
  • 在局部变量的类型(T tmp)。

更多关于泛型的信息,请查看运用泛型方法