1. 程式人生 > 實用技巧 >【Flutter 1-12】Flutter手把手教程Dart語言——什麼是泛型和泛型的使用場景

【Flutter 1-12】Flutter手把手教程Dart語言——什麼是泛型和泛型的使用場景

作者 | 弗拉德
來源 | 弗拉德(公眾號:fulade_me)

泛型

如果你檢視陣列的API文件,你會發現陣列List的實際型別為List<E><> 符號表示陣列是一個泛型(或引數化型別)通常使用一個字母來代表型別引數,比如E、T、S、K 和 V 等等。

為什麼使用泛型?

泛型常用於需要要求型別安全的情況,但是它對程式碼執行也有好處:

  • 適當地指定泛型可以更好地幫助程式碼生成。
  • 使用泛型可以減少程式碼重複。

比如你想宣告一個只能包含String型別的陣列,你可以將該陣列宣告為List<String>,這表示只能包含字串型別的陣列。這樣的話就可以很容易避免因為在該陣列放入非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);
}

不久後你可能又會想專門為String類物件做一個快取,於是又有了專門為String做快取的類:

/// 另外一個抽象類
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字面量也可以是引數化的。定義引數化的List只需在中括號前新增<type>;定義引數化的Map只需要在大括號前新增 <keyType, valueType>

var names = <String>['小芸', '小芳', '小民'];
var uniqueNames = <String>{'小芸', '小芳', '小民'};
var pages = <String, String>{
  'index.html': '主頁',
  'robots.txt': '網頁機器人提示',
  'humans.txt': '我們是人類,不是機器'
};

使用型別引數化的建構函式

在呼叫構造方法時也可以使用泛型,只需在類名後用尖括號<...>將一個或多個型別包裹即可:

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

下面程式碼建立了一個鍵為Int型別,值為View型別的Map物件:

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

泛型集合以及它們所包含的型別

Dart的泛型型別是固化的,這意味著即便在執行時也會保持型別資訊:

var names = List<String>();
names.addAll(['小芸', '小芳', '小民']);
print(names is List<String>); // true

限制引數化型別

有時使用泛型的時候可能會想限制泛型的類型範圍,這時候可以使用extends關鍵字:

class Foo<T extends SomeBaseClass> {
  // 具體實現……
  String toString() => "'Foo<$T>' 的例項";
}
class Extender extends SomeBaseClass {...}

這時候就可以使用SomeBaseClass或者它的子類來作為泛型引數:

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

這時候也可以指定無引數的泛型,這時無引數泛型的型別則為 Foo<SomeBaseClass>

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

將非SomeBaseClass的型別作為泛型引數則會導致編譯錯誤:

/// 這樣寫是會報錯的
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