泛型

Last updated: ... / Reads: 46 Edit

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

为什么要使用泛型?

类型安全通常需要泛型,但它们比仅仅允许代码运行有更多好处:

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

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

×static analysis:erro/warning
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

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

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

使用集合文字

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

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

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

通用集合及其包含的类型

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

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

相比之下,Java 中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。在Java中,你可以测试一个对象是否是一个List,但你不能测试它是否是一个 List

限制参数化类型

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

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

class Foo<T extends Object> {
  // Any type provided to Foo for T must be non-nullable.
}

您可以将 extends 与除 Object 之外的其他类型一起使用。下面是扩展 SomeBaseClass 的示例,以便可以在 T 类型的对象上调用 SomeBaseClass 的成员:

class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

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

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

不指定通用参数也可以:

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

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

var foo = Foo<Object>();

使用通用方法

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

T first<T>(List<T> ts) {
  // Do some initial work or error checking, then...
  T tmp = ts[0];
  // Do some additional checking or processing...
  return tmp;
}

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

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

Comments

Make a comment