iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 23
0
Software Development

Kotlin 島深度之旅 30 天系列 第 23

[Day 23] Kotlin Journey - Generic (泛型) 基礎

今天要來介紹 Kotlin 中的泛型(Generic)

Why Generic?

當我們在寫程式的時候,是不是常常遇到程式碼都一模一樣,但卻可能因為參數的型態,而必須分成不一樣的程式呢?

像是下面這兩段程式,其實內容都一樣,只是參數的型態一個是 String, 一個是 Int

fun acceptString(obj: String): String {
    println("acceptString:$obj")
    return obj
}

fun acceptInt(obj: Int): Int {
    println("acceptString:$obj")
    return obj
}

接著來看看如何呼叫這兩個方法

因為這裡想要做的操作是加上 10 在印出結果

val str = acceptString("100")
println("acceptString output:" + str.toInt().plus(10)) // acceptString output:110

val int = acceptInt(100)
println("acceptInt output:" + int.plus(10)) // acceptInt output:110

所以針對第一個 acceptString("100"),因為得到的是一個字串,所以必須把字串轉成 Int 後再加上 10,也就會是 str.toInt().plus(10)

第二個 acceptInt(100) 一樣是整數,所以直接 int.plus(10) 就可以

試試看 Any

當然聰明的我們,是不是就想到可以用 Any 不是嗎?Any 是所有物件的父親

改成這樣後應該都可以成功吧?

fun acceptAny(obj: Any): Any {
    println("acceptAny:$obj")
    return obj
}

以第一個例子字串來試試看,這裡因為 anything 是 Any 型態,所以根本不能用 toInt()

https://ithelp.ithome.com.tw/upload/images/20201002/20129902Vd9lZ8Hpge.png

那試試看用強制轉型 (anything as Int).plus(10) 把 Any 轉成 Int,但竟然會噴錯

https://ithelp.ithome.com.tw/upload/images/20201002/20129902Oc7057id1j.png

這樣成功輸出了,先把 Any 轉成 String 在做 toInt()

val anything = acceptAny("100")
println("any output:" + (anything as String).toInt().plus(10)) // any output:110

所以看了以上的做法是不是覺得很不保險,又很麻煩呢?

Generic (泛型) 會是你的救星

所以剛剛的程式改成以下就可以順利執行啦!

fun <T> acceptT(obj: T): T {
    println("acceptT:$obj")
    return obj
}
// call generic
val t = acceptT("100")
println("generic output:" + t.toInt().plus(10)) // generic output:110

Generic function (泛型函數)

剛剛那樣的宣告方式就是泛型函數

<T> 就是我們要使用的泛型的型態代號,不一定要使用T,要用什麼英文字母都可以,按照以往的慣例會用大寫字母表示,一般來說會用T來宣告。

單個泛型型態

所以像這裡輸入的參數和回傳值的型態,都是一樣,就都會使用T。
https://ithelp.ithome.com.tw/upload/images/20201002/20129902tuRHxTls6A.png

兩種泛型型態,輸入和回傳的型態不一樣

下面的例子,輸入和回傳的型態不一樣,輸入的參數型態是 T,回傳的型態想要是 R,就會像這樣宣告

fun <T, R> acceptTAndReturnR(obj: T): R {
    return "test" as R
}

mutableMapOf() 的原始碼

也還有一些慣例的英文命名,像是如果是 key,就會用 K,value 就會用 V

像是 mutableMapOf() 的原始碼,就使用了 K, V 來代表 key, value

https://ithelp.ithome.com.tw/upload/images/20201002/20129902dbz6VIcFi0.png


Generic class (泛型類別)

這裡宣告了一個 Data 的類別 <V> 就會是泛型的型態,constructor 的變數也是這個泛型型態

https://ithelp.ithome.com.tw/upload/images/20201002/201299022dzgBQYYYZ.png

使用起來就會像這樣

val dataLong = Data<Long>(1000L)
val dataStr = Data<String>("data test")

MutableList 的原始碼

來看一下 MutableList 的原始碼,會發現也用了泛型,讓任何資料都可以塞入 MutableList, MutableList 還繼承了 List 和 MutableCollection 這兩個介面。
https://ithelp.ithome.com.tw/upload/images/20201002/20129902f8sefp6LCj.png


Type parameter constraints (泛型型態的限制)

如果想要設定泛型型態的上界(upper bound)

像是下例,可以針對 T 這個泛型型態限制的上界就會是 Number,也就是 T 可以是 Int, Long, Double...等型態來做加總,最後回傳 T型態的結果

fun <T: Number> sum(num1: T, num2: T) :T {
    return num1.toDouble().plus(num2.toDouble()) as T
}

如果給了 String 是不合法的!

https://ithelp.ithome.com.tw/upload/images/20201002/20129902hNxk5C76WB.png

如果想要多個上界(upper bound)

就要使用 where

fun <T> appendDot(seq: T): T 這部分是正常泛型宣告

where T : CharSequence, T : Appendable 這部分就定義了兩個上界,限定 T 要是 CharSequenceAppendable 這兩個 interface 的物件實作,才能傳入這個函數

fun <T> appendDot(seq: T): T
        where T : CharSequence, T : Appendable {
    if (!seq.endsWith('.')) {
        seq.append('.')
    }
    return seq
}

因為下面這兩個都是 class 因為只允許單一繼承,所以不行,上界中 class 只能一個,要多個要用介面。

https://ithelp.ithome.com.tw/upload/images/20201002/20129902UuZ5i4W2qj.png

隱藏的上界

其實所有的泛型都有一個隱藏的上界,那就是 Any?

這樣的上界會有什麼需要注意的呢?

像這樣的程式,因為隱藏的上界是 Any? ,所以會提示說要用 ?.,因為 obj1 會是 nullable 的型態

https://ithelp.ithome.com.tw/upload/images/20201002/20129902wuoYaSg1vR.png

修正後會是這樣

fun <T> compareObj(obj1: T, obj2: T): Boolean? {
    return obj1?.equals(obj2)
}

如果不想處理 nullable,可以把上界直接設成 Any

fun <T : Any> compareObj2(obj1: T, obj2: T): Boolean? {
    return obj1.equals(obj2)
}

以上就是今天的內容!謝謝大家!

泛型還有 out, in 和 reified 這三個東西,明天再繼續探討!

今日練習的程式在這: 請點我


上一篇
[Day 22] Kotlin Journey - Map & leetcode 相關練習
下一篇
[Day 24] Kotlin Journey - Generic (泛型) : out, in, star-projections, reified
系列文
Kotlin 島深度之旅 30 天31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言