內容

如果您查看基本陣列類型 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 是替身類型。它是一個佔位符,你可以將其視為開發人員稍後會定義的類型。

使用集合文字

#

清單、集合和對應關係文字可以參數化。參數化文字就像你已經看過的文字一樣,只不過你在開括號前加上 <類型>(對於清單和集合)或 <鍵類型, 值類型>(對於對應關係)。以下是使用類型化文字的範例

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?),來確保類型為非 Null。

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

除了 Object 之外,你也可以對其他類型使用 extends。以下是如何延伸 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)。