【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
。