Kotlin類與對象篇(7)--泛型(1)


歡迎關注 二師兄Kotlin
轉載請注明出處 二師兄kotlin


跟Java一樣,在Kotlin中的類也可以擁有泛型參數:

class Box<T>(t: T) {
    var value = t
}

通常,為了創建這樣一個類的實例,我們需要提供類型參數:

val box: Box<Int> = Box<Int>(1)

但是如果這個參數是可以被推斷出來的,我們就可以忽略類型:

  val box = Box(1) // 1 has type Int, so the compiler figures out that we are talking
  about Box<Int>

泛型之可變性

Java類型系統最復雜的特性之一,就是通配符。但是Kotlin中一個都沒有,取而代之的是兩種其他實現:: declaration-site可變類型預測(type projections)。

首先,讓我們想一下,為什么Java需要如此難以理解的通配符。這個問題在Effective Java的第28節有解釋:利用有限制的通配符來提高API的靈活性。首先,Java中泛型的參數化類型是不可變的(invariant),這意味著List<String>并不是List<Object>的子類型。為什么會這樣? 如果List是不可變的,那么它對比于Java中數組(arrays)就沒有任何優勢了,下面這段代碼將會帶來編譯異常以及運行時異常:

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! 這就是即將引入的問題的原因。Java禁止這樣!
objs.add(1); // 我們向一個包含`String`的列表添加了一個`Integer` 
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String

所以,Java禁止這樣做其實是為了保證運行時安全。但是它有這樣的一些啟示,比如,考慮一下Collection接口的addAll()方法,這個方法的簽名是什么呢?直觀來看覺得可能會是這樣的:

// Java
interface Collection<E> ... {
    void addAll(Collection<E> items);
}

但是,我們卻不能做下面這件很簡單的事(盡管它看起來已經相當安全了):

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
    to.addAll(from); // !!! Would not compile with the naive declaration of addAll:
                     // Collection<String> is not a subtype of Collection<Object>
}

(在Java中, 我們學習這一節非常之不容易, 參見Effective Java, 第25節:列表優先于數組( Prefer lists to arrays))

這就是為什么addAll()的實際的簽名函數會變成下面這個樣子:

// Java
interface Collection<E> ... {
    void addAll(Collection<? extends E> items);
}

通配符類型參數(wildcard type argument)? extends T表明這個方法接受 T子類型的對象集合,并非T本身。這意味著,可以從列表中安全的讀取T(集合中所有的元素都是T的一個子類),但是我們無法寫入因為我們并不知道哪些類是T的子類。因為有了這種限制,我們渴望這種行為: Collection<String>Collection<? extends Object>的子類。從表面意義來看,通過extends-bound(向上限制)修飾的通配符使得類型可協變

理解這個技巧什么能工作的關鍵其實很簡單:如果你僅僅是從一個集合中取元素, 比如使用一個String集合并且從中讀取Object是工作正常的。相反的,如果你需要向集合中添加元素,比如使用一個Object的集合并且向其中添加String,你可以利用Java中的另一個通配符 List<? super String>List<Object>的超類。

這被成為 逆變(contravariance),你只能使用String作為參數在List<? super String>上調用方法( 你可以調用add(String)或者 set(int, String)),然而,如果當你調用List<T>的一些函數來返回T的話,你將會得到一個Object,而不是String。

Joshua Bloch稱:這些對象你只能從生產者(Producers)中讀取,只能在消費者(Consumers)中寫入,他如此推薦:“為了最大程度的靈活性,在輸入參數時使用通配符類型來表示生產者或者消費者”,可以使用下面的方法來提高記憶:

PECS stands for Producer-Extends, Consumer-Super.

注意:如果你使用一個Object生產者,List<? extends Foo>,你將不會被允許在這個對象上調用add()set(),但是這并不意味著這個對象是不可變得(immutable):比如,沒有什么可以阻止你調用clear()來移除這個list的所有item,因為clear()并不需要任何參數。通配符唯一保證的事情就是類型安全(type safety)。不可變性(Immutability)完全是另外一件事。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容