Kotlin 中的 in and out

fantasy1022
7 min readJun 27, 2020

--

來源:https://zh.wikipedia.org/wiki/In-N-Out%E6%BC%A2%E5%A0%A1

一開始看到 Kotlin 介紹時,看到了 in out 的介紹,讓我想到了兩年前去 Google I/O 時,去吃的在地美食 In-N-Out 漢堡,漢堡真的好吃 XD。後來買了深入淺出 Kotlin,有章節在介紹這個泛型的用法,看完後想來做個整理,覺得會比較能理解。

先來看書上是怎麼解釋 out 的:如果你希望能夠在使用泛型超型態物件的地方使用泛型子型態物件,可以在泛型型態前面加上 out,這個泛型型態就是協變的(covariant)。

看完覺得有點抽象,還是來看看程式碼好了:

舉例來說我們有檔案(Track)、音檔(Audio)、歌曲(Song),有各自的類型,歌曲繼承音檔,然後音檔繼承檔案的類型。

然後設定銷售者介面,注意在銷售者介面的泛型,有加入 out,在使用的時候就可以看出用途,然後各別類型的銷售者,會長這樣:

賣音檔的商店,也是屬於檔案的商店,賣歌曲的商店,屬於賣檔案的商店。

從上面的程式碼可以了解到 out 的用途,在沒有家 out 的情況下,會出現紅線,無法通過 Compile,這時候再去看定義,就大概能了解了,可以用子型態(Retailer<Audio>) 來取代超型態 (Retailer<Track>。

其實在 Kotlin 的內建程式也大量使用到 out,例如:Collection

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/

List 本身在泛型就有加入協變了,因此加上面的 Retailer Interface 換成 List,還是可以正常使用。

Kotlin 程式碼有特別的功能的時候,就會想看一下轉換成 Java 是怎麼寫的,看到了熟悉的寫法 XD,就是轉型,那如果我們不用 out,改用轉型的寫法,是不是也可以呢?

在 Retailer 的泛型拿掉 out 後,改成這樣使用也是可以的。

val retailer2: Retailer<Track> = AudioRetailer() as Retailer<Track>

從這個結果可以幫助記憶,Kotlin 使用了 Out,簡化了一些語法,讓開發者能寫出更簡潔的程式碼!

out 除了上面的用途外,使用的位置是有限定的,只能用在 “out”(離開的),ex: 例如函式回傳的回傳型態,不能用在傳入的參數值,看完覺得有點抽象,不過旁邊馬上有練習題,可以來實作,幫助理解。

深入潛出 Kotlin P.311

大家可以想一下答案 XD,原來加入 out 後,不能當成函式的參數和 var 屬性的型態使用。

深入潛出 Kotlin P.312

了解 out 接著來看 in 吧。當我們在泛型型態前面加上 in 時,那個泛型型態就是反變的 (contravariant),就可以用超型態來取代子型態,是上述協變的相反。

銷售者介面設定完,我們接者設定消費者介面,分別是買檔案消費者、音檔消費者和歌曲消費者:

歌曲的消費者,也是當檔案的消費者,也是音檔的消費者。

可以看到 in 使用在的位置是進入的位置使用,例如:函式的參數值,明顯和 out 不同。實際來看看書上的問題吧:

深入潛出 Kotlin P.320
深入潛出 Kotlin P.322

看完使用限制後,我就想到了 List 的泛型有加 out,但除了回傳值外,也是會有參數傳入值啊,但不是 add XDDD,因為 List 是 immutable 的,像是 indexOf 會傳入物件,理論上以 out 的限制來說是不行的,從這邊的原始碼,看到加了一個 annotation: @UnsafeVariance ,關於這個議題,我們之後再做一集視頻解說 XDD

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index-of.html

看了 in 和 out 後,如果泛型型態都沒有加這兩個前綴詞,這個型態就是不變的 (invariant),不變型態只能接收特定型態的參考。

深入潛出 Kotlin P.319

看到這段就想複習一下那 Java 的 wildcard 又是什麼,除了演算法題目外,有點久沒寫 Java 了 XDD,Kotlin 用 in / out 達成同一個概念,但有比較漂亮的語法。

範例有可能還是看不懂 XD,但還是根據上面的內容,可以做個結論,之後看到相關的 Library 或是原始碼有用到,會知道這個寫法可以達成什麼功能。

  • in:父類泛型對象可以賦值給子類泛型對象。
  • out:子類泛型對象可以賦值給父類泛型對象。
Ref: In and out type variant of Kotlin

--

--