Kotlin Tutorial(18)泛型的設計與應用 by Michael | CodeData
top

Kotlin Tutorial(18)泛型的設計與應用

分享:

Kotlin Tutorial(17)存取修飾子與資料類別 << 前情

泛型(generic)對Kotlin與Java都是一個很重要的特性,它可以節省許多冗長與重複的程式碼,很多API使用泛型的設計,例如經常使用的集合(collection)。在瞭解泛型的設計與應用以後,也可以容易的使用泛型API執行需要的工作。

沒有使用泛型

應用程式在處理各種資料的時候,可能會設計一個像這樣的類別,讓陣列的使用可以方便一些:

/* net.macdidi5.kotlin.tutorial.ch18.StringContainer.kt */

package net.macdidi5.kotlin.tutorial.ch18

class StringContainer(private val size : Int) {

    // 建立指定元素個數的陣列
    private val arr = arrayOfNulls<String>(size)
    // 目前的索引編號
    private var index : Int = 0

    // 加入一個元素
    fun add(element : String) {
        if (index < size) {
            arr[index] = element
            index++
        }
    }

    // 取得指定索引編號的元素
    fun get(index : Int) : String? {
        var result : String? = null

        if ( index in 0 until size ) {
            result = arr[index]
        }

        return result
    }

}

下面的程式碼示範使用StringContainer類別處理字串資料:

/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo01.kt */

package net.macdidi5.kotlin.tutorial.ch18

fun main(args: Array<String>) {

    // 建立包裝字串的容器物件
    val sc = StringContainer(3)

    // 加入字串元素
    sc.add("Simon")
    sc.add("Mary")
    sc.add("John")

    for (i in 0..2) {
        println("$i : ${sc.get(i)}")
    }

    ...

}

如果應用程式也需要使用上面的作法處理Item物件,就需要另外設計一個這樣的類別:

/* net.macdidi5.kotlin.tutorial.ch18.ItemContainer.kt */

package net.macdidi5.kotlin.tutorial.ch18

class ItemContainer(private val size : Int) {

    // 建立指定元素個數的陣列
    private val arr = arrayOfNulls<Item>(size)
    // 目前的索引編號
    private var index : Int = 0

    // 加入一個元素
    fun add(element : Item) {
        if (index < size) {
            arr[index] = element
            index++
        }
    }

    // 取得指定索引編號的元素
    fun get(index : Int) : Item? {
        var result : Item? = null

        if ( index in 0 until size ) {
            result = arr[index]
        }

        return result
    }

}

下面的程式碼示範使用ItemContainer類別處理Item資料:

/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo01.kt */

package net.macdidi5.kotlin.tutorial.ch18

fun main(args: Array<String>) {

    ...

    // 建立包裝Item的容器物件
    val ic = ItemContainer(3)

    // 加入Item元素
    ic.add(Item(101, "Hello", "Kotlin"))
    ic.add(Item(102, "Hi", "Android"))
    ic.add(Item(103, "Chao", "Java"))

    for (i in 0..2) {
        println("$i : ${ic.get(i)?.details}")
    }

}

比較StringContainer與ItemContainer,你可以注意到它們幾乎是一樣的,這類的設計方式,就會發生程式碼重複的狀況,對於程式開發與維護來說,都是很不好的。

使用泛型宣告

如果一個類別或介面,它們需要處理的多種不同的資料型態,你可以在類別或介面名稱後面,使用「<泛型代碼>」加入泛型的宣告。下面是一個泛型宣告的範例:

class GenericClass<T> {
    private var value : T? = null

    fun get() : T? {
        return value
    }

    fun set(value : T?) {
        this.value = value
    }
}

使用這個類別的時候,可以在類別名稱後面指定泛型的型態:

val stringGC = GenericClass<String>()

在宣告GenericClass的時候,根據你在「<」和「>」之間指定的型態,這個類別會變成下面的樣子:

class GenericClass<String> {
    private var value : String? = null

    fun get() : String? {
        return value
    }

    fun set(value : String?) {
        this.value = value
    }
}

如果在宣告GenericClass的時候,指定泛型的型態為Item:

val itemGC = GenericClass<Item>()

對itemGC這個物件來說,GenericClass類別會變成下面的樣子:

class GenericClass<Item> {
    private var value : Item? = null

    fun get() : Item? {
        return value
    }

    fun set(value : Item?) {
        this.value = value
    }
}

根據上面說明的範例,使用泛型宣告的GenericClass類別,可以提供非常大的靈活性,而且避免重複的程式碼。泛型代碼可以是任何有效的識別字,一般的作法會使用下面的規則:

  • 一個大寫的字母
  • 依照用途選擇適合的字母,例如:
    • T:型態
    • S、U、V…:第二、三、四…種型態
    • E:元素
    • K:索引鍵型態
    • V:資料型態

下面的範例也是一個正確的泛型宣告,不過並不建議使用像「QQQ」這樣的泛型代碼:

class GenericClass<QQQ> {
    private var value : QQQ? = null

    fun get() : QQQ? {
        return value
    }

    fun set(value : QQQ?) {
        this.value = value
    }
}

泛型宣告範例

下面的Container類別使用陣列包裝同樣型態的資料,不過類別提供比較簡易的用法,因為使用泛型的設計,Container可以包裝各種型態的資料:

/* net.macdidi5.kotlin.tutorial.ch18.Container.kt */

package net.macdidi5.kotlin.tutorial.ch18

class Container<T>(private val size : Int) {

    // 建立指定元素個數的陣列
    private val arr = arrayOfNulls<Any?>(size)
    // 目前的索引編號
    private var index : Int = 0

    // 加入一個元素
    fun add(element : T) {
        if (index < size) {
            arr[index] = element
            index++
        }
    }

    // 取得指定索引編號的元素
    fun get(index : Int) : T? {
        var result : T? = null

        if ( index in 0 until size ) {
            result = arr[index] as T
        }

        return result
    }

}

下面的範例程式使用Container類別包裝字串與Item資料:

/* net.macdidi5.kotlin.tutorial.ch18.ContainerDemo02.kt */

package net.macdidi5.kotlin.tutorial.ch18

fun main(args: Array<String>) {

    // 建立包裝元素的容器物件,使用泛型設定元素為字串
    val sc = Container<String>(3)

    // 加入字串元素
    sc.add("Simon")
    sc.add("Mary")
    sc.add("John")

    for (i in 0..2) {
        println("$i : ${sc.get(i)}")
    }

    println()

    // 建立包裝元素的容器物件,使用泛型設定元素為Item
    val ic = Container<Item>(3)

    // 加入Item元素
    ic.add(Item(101, "Hello", "Kotlin"))
    ic.add(Item(102, "Hi", "Android"))
    ic.add(Item(103, "Chao", "Java"))

    for (i in 0..2) {
        println("$i : ${ic.get(i)?.details}")
    }

}

泛型函式

應用程式在提供各種功能的時候,可能會有下面這樣的需求:

/* net.macdidi5.kotlin.tutorial.ch18.GenericFunction01.kt */

package net.macdidi5.kotlin.tutorial.ch18

// 接收兩個字串陣列參數,結合兩個陣列以後傳回新的字串陣列
fun combineArray(a1 : Array<String>, a2 : Array<String>) : Array<String?> {
    val newSize = a1.size + a2.size
    val result = a1.copyOf(newSize)

    for ( (i, v) in a2.withIndex() ) {
        result[ i + a1.size ] = v
    }

    return result
}

// 接收兩個Item陣列參數,結合兩個陣列以後傳回新的Item陣列
fun combineArray(a1 : Array<Item>, a2 : Array<Item>) : Array<Item?> {
    val newSize = a1.size + a2.size
    val result = a1.copyOf(newSize)

    for ( (i, v) in a2.withIndex() ) {
        result[ i + a1.size ] = v
    }

    return result
}

...

fun main(args: Array<String>) {
    // 建立兩個字串陣列
    val names01 = arrayOf("Simon", "Mary")
    val names02 = arrayOf("Sam", "John")

    // 結合兩個字串陣列
    val namesAll = combineArray(names01, names02)

    for ( (i, v) in namesAll.withIndex()) {
        println("$i: $v")
    }

    println()

    val item01 = Item(101, "Hello", "Kotlin")
    val item02 = Item(102, "Hi", "Android!")
    val item03 = Item(103, "Chao", "Java")

    // 建立兩個Item陣列
    val items01 = arrayOf(item01, item02)
    val items02 = arrayOf(item03)

    // 結合兩個Item陣列
    val itemsAll = combineArray(items01, items02)

    for ( (i, v) in itemsAll.withIndex()) {
        println("$i: ${v?.details}")
    }

    ...

}

上面範例的combineArray函式在處理不同型態陣列的時候,需要宣告不同的函式,這樣的設計方式看起來應該就會覺得不太好。如果應用程式有這類的需求,可以使用泛型函式的作法。例如下面的泛型函式範例:

fun <T> genericFunction(p : T) : T {
    val value : T = p
    return value
}

在呼叫上面泛型函式的時候,可以經由參數決定泛型函式的型態,因為傳送給函式的參數為字串,所以泛型函式的型態為String,這個泛型函式的回傳型態也是String:

genericFunction("Hello")

根據上面的說明與範例,這個函式的宣告會像下面的樣子:

fun <String> genericFunction(p : String) : String {
    val value : String = p
    return value
}

如果呼叫這個泛型函式的時候,傳送的參數為Item型態的物件:

genericFunction(Item(101, "Hello", "Kotlin"))

這個函式的宣告會像下面的樣子:

fun <Item> genericFunction(p : Item) : Item {
    val value : Item = p
    return value
}

瞭解泛型函式的基本概念以後,就可以設計執行結合陣列工作的函式:

/* net.macdidi5.kotlin.tutorial.ch18.GenericFunction01.kt */

package net.macdidi5.kotlin.tutorial.ch18

...

// 接收兩個泛型陣列,結合兩個陣列以後傳回新的泛型陣列
fun <T> combineArrayGeneric(a1 : Array<T>, a2 : Array<T>) : Array<T?> {
    val newSize = a1.size + a2.size
    val result = a1.copyOf(newSize)

    for ( (i, v) in a2.withIndex() ) {
        result[ i + a1.size ] = v
    }

    return result
}

fun main(args: Array<String>) {
    // 建立兩個字串陣列
    val names01 = arrayOf("Simon", "Mary")
    val names02 = arrayOf("Sam", "John")

    ...

    val item01 = Item(101, "Hello", "Kotlin")
    val item02 = Item(102, "Hi", "Android!")
    val item03 = Item(103, "Chao", "Java")

    // 建立兩個Item陣列
    val items01 = arrayOf(item01, item02)
    val items02 = arrayOf(item03)

    ...

    // 呼叫泛型版本的函式,由傳遞的參數決定泛型為String
    val names = combineArrayGeneric(names01, names02)

    for ( (i, v) in names.withIndex()) {
        println("$i: $v")
    }

    println()

    // 呼叫泛型版本的函式,由傳遞的參數決定泛型為Item
    val items = combineArrayGeneric(items01, items02)

    for ( (i, v) in items.withIndex()) {
        println("$i: ${v?.details}")
    }

}

下一步

應用程式經常需要處理數量比較多一些的資料,除了固定元素個數的陣列外,接下來準備認識應用程式經常使用的集合架構。

相關的檔案都可以在GitHub瀏覽與下載。

https://github.com/macdidi5/Kotlin-Tutorial

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

熱門論壇文章

熱門技術文章