iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
0

什麼是泛型?

前面介紹 List、Set、Map,它們可以使用不同的型別,

List 的 API 文件中,發現它的型別定義為 List;而 Set 是 Set;Map 則 Map<K, V>。

在 <...> 裡面定義的不是基本型別,而是英文字母,這就是泛型。

其中, E 代表的是集合類中的元素, KV 分別代表的是 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);
}

等等... 如果針對每一個型別都需要新建立一個類別,那麼就會產生出很多重複的程式碼。

我們可以將這個類別改為用泛型的方式來設計:

  • 將類型名稱後面加上角括弧,並加上泛型型別的識別字母 T。
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 則是用在單一型別的參數。 參考


上一篇
Day19:靜態變數 (Static variable)、靜態方法 (Static method) 以及 頂層函數 (Top-level functions)
下一篇
Day 21:像呼叫函式一樣的呼叫類別吧。(Callable class)
系列文
Dart 語言 - 開啟 Flutter 的鑰匙30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言