前面介紹 List、Set、Map,它們可以使用不同的型別,
在 List 的 API 文件中,發現它的型別定義為 List;而 Set 是 Set;Map 則 Map<K, V>。
在 <...> 裡面定義的不是基本型別,而是英文字母,這就是泛型。
其中, E
代表的是集合類中的元素, K
與 V
分別代表的是 Key(鍵) 與 Value (值)。
以 List 為範例,我們可以將任意型別的值存入 List 中,接者如果我們嘗試存入不同的型別,就會出現編譯錯誤,這就是型別安全。
如果我們不使用泛型的方式來設計 List,那麼我們就必須要為所有的型別設計一份 List,而利用泛型則可以設計出更為通用的類別、函數。
var city = List<String>();
city.addAll(['Taipei', 'Tokyo', 'San Francisco']);
city.add(42); // Error
另一個理由是,可以減少重複的程式碼。
假設我們有一個類別如下:
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
然後,你發現你希望想要一個 String 版本的,於是你新建一個類別如下:
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
接者,你又想要建立一個 num 版本的,所以又建立了
abstract class NumCache {
num getByKey(String key);
void setByKey(String key, num value);
}
等等... 如果針對每一個型別都需要新建立一個類別,那麼就會產生出很多重複的程式碼。
我們可以將這個類別改為用泛型的方式來設計:
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
其中,T為替代類型,作為開發人員稍後定義的型別。
我們將型別定義為 T 之後,在類別裡面就可以使用 T 這個泛型型別是別字母。
Dart 的泛型型別是整齊的,意思是它會將型別資訊帶到執行期。與之對比的是 Java ,在 Java 編譯時期將值存入之後,就會將型別刪除。所以無法測試型別。
var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); //true
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
if(list instanceof ArrayList<String>){} //error
if(list instanceof ArrayList){} //OK
用泛型設計時,有些時候並不想設計給全部的型別使用,只想開放給某類別及其子類別使用,該怎麼做呢?
使用關鍵字 extends
將泛型型別繼承某類別,那麼該類別就只能使用某類別或其子類別。
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
我們定義了泛型類別 Foo
,並且限制它的泛型型別為 SomeBaseClass
,所以 Foo 類別可以接受 SomeBaseClass 及其子類別 Extender
。
var someBaseClassFoo = Foo<SomeBaseClass>(); //OK
var extenderFoo = Foo<Extender>(); //OK
沒有使用角括弧指定型別也可以:
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
但是,使用其他型別就不被允許
var foo = Foo<Object>(); //Error
泛型除了可以用在類別之外,還可以用在函數上。
定義函數的語法如下:
return_type function_name(arguments){}
我們將回傳型別 return_type 改為T,並且在函數名稱後面用角括弧定義泛型的型別。
範例:
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;
}
泛型,是既熟悉又陌生的朋友,我們在集合類都能看到它的身影。
有了泛型,我們能夠設計出通用的程式碼,並且包含型別安全的特性。
有了泛型,我們能夠減少重複的程式碼。
泛型的型別識別字母雖然可以任意使用,但是一般我們都會將 E
使用在 集合類的元素, K
用在 Map 類的鍵 (Key), V
用在 Map類的值 (Value),而 T
則是用在單一型別的參數。 參考