目录

泛型

如果您查看基本数组类型的API文档, List ,您会发现该类型实际上是 List<E> 。<... > 表示法将 List 标记为 泛型 (或 参数化 )类型——一种具有形式类型参数的类型。 按照惯例 ,大多数类型变量都使用单个字母名称,例如 E、T、S、K 和 V。

为什么使用泛型?

#

泛型通常是类型安全所必需的,但它们的好处不仅仅是允许您的代码运行:

  • 正确指定泛型类型可以生成更好的代码。
  • 您可以使用泛型来减少代码重复。

如果您打算让列表只包含字符串,您可以将其声明为 List<String> (读作“字符串列表”)。这样,您、您的同事程序员和您的工具就可以检测到将非字符串赋值给列表可能是一个错误。这是一个例子:

✗ static analysis: failuredart
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // 错误

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

dart
abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

您发现您想要此接口的特定于字符串的版本,因此您创建了另一个接口:

dart
abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

稍后,您决定想要此接口的特定于数字的版本……您明白了。

泛型类型可以避免您创建所有这些接口的麻烦。相反,您可以创建一个接受类型参数的单个接口:

dart
abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

在此代码中,T 是占位符类型。您可以将其视为开发人员稍后将定义的类型。

使用集合字面量

#

列表、集合和映射字面量可以参数化。参数化字面量就像您已经看到的字面量一样,只是您在左括号之前添加了<type>(对于列表和集合)或<keyType, valueType>(对于映射)。这是一个使用类型化字面量的示例:

dart
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'
};

使用构造函数的参数化类型

#

要使用构造函数指定一个或多个类型,请将类型放在类名后的尖括号( <...> )中。例如:

dart
var nameSet = Set<String>.from(names);

以下代码创建了一个具有整数键和 View 类型值的映射:

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

泛型集合及其包含的类型

#

Dart 泛型类型是 具体化 的,这意味着它们在运行时携带其类型信息。例如,您可以测试集合的类型:

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

限制参数化类型

#

在实现泛型类型时,您可能希望限制可以作为参数提供的类型,以便参数必须是特定类型的子类型。您可以使用 extends 来实现。

一个常见的用例是通过使其成为 Object 的子类型来确保类型不可为空(而不是默认的 Object? )。

dart
class Foo<T extends Object> {
  // 提供给 Foo 的任何类型 T 都必须是不可为空的。
}

您可以将 extendsObject 以外的其他类型一起使用。这是一个扩展 SomeBaseClass 的示例,以便可以对类型为 T 的对象调用 SomeBaseClass 的成员:

dart
class Foo<T extends SomeBaseClass> {
  // 实现代码在此处...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

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

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

指定没有泛型参数也是可以的:

dart
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'

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

✗ static analysis: failuredart
var foo = Foo<Object>() ;

使用泛型方法

#

方法和函数也允许类型参数:

dart
T first <T>(List<T> ts) {
  // 执行一些初始工作或错误检查,然后...
  T tmp = ts[0];
  // 执行一些额外的检查或处理...
  return tmp;
}

这里 first 上的泛型类型参数( <T> )允许您在多个地方使用类型参数 T

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