目錄

泛型

如果您查看基本陣列類型 List 的 API 文件,您會發現該類型實際上是 List<E><...> 符號表示 List 是一個泛型(或參數化)類型,這是一種具有形式類型參數的類型。按照慣例,大多數類型變數都有單字母名稱,例如 E、T、S、K 和 V。

為什麼要使用泛型?

#

泛型通常是類型安全所必需的,但它們不僅僅是讓您的程式碼運作,還具有更多優點

  • 正確指定泛型類型可以產生更好的程式碼。
  • 您可以使用泛型來減少程式碼重複。

如果您希望清單僅包含字串,您可以將其宣告為 List<String>(讀作「字串清單」)。這樣一來,您、您的同事程式設計師和您的工具就可以偵測到將非字串指派給清單可能是錯誤的。這是一個範例

✗ 靜態分析:失敗dart
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error

使用泛型的另一個原因是減少程式碼重複。泛型讓您可以在多種類型之間共享單一介面和實作,同時仍然利用靜態分析。例如,假設您建立一個用於快取物件的介面

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 是預留類型。它是一個預留位置,您可以將其視為開發人員稍後將定義的類型。

使用集合常值

#

清單、集合和 Map 常值可以參數化。參數化常值就像您已經看過的常值一樣,只是您在左方括號之前添加了 <type>(對於清單和集合)或 <keyType, valueType>(對於 Map)。以下是使用類型常值的範例

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 類型值的 Map

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> {
  // Any type provided to Foo for T must be non-nullable.
}

您可以使用 extendsObject 以外的其他類型。以下是擴展 SomeBaseClass 的範例,因此可以在 T 類型的物件上呼叫 SomeBaseClass 的成員

dart
class Foo<T extends SomeBaseClass> {
  // Implementation goes here...
  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 類型都會產生錯誤

✗ 靜態分析:失敗dart
var foo = Foo<Object>();

使用泛型方法

#

方法和函式也允許類型引數

dart
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

  • 在函式的回傳類型 (T) 中。
  • 在引數的類型 (List<T>) 中。
  • 在區域變數的類型 (T tmp) 中。