Kotlin Tutorial(17)存取修飾子與資料類別 by Michael | CodeData
top

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

分享:
tags: kotlin

Kotlin Tutorial(16)介面與實作(下) << 前情

物件導向基本的概念有封裝、繼承與多型,設計應用程式的時候,除了撰寫類別與函式提供需要的功能外,也要注意架構的完整與資料正確性,所以就需要加入封裝的作法。另外應用程式通常需要一些主要類別,Kotlin提供方便的資料類別,可以大幅度簡化程式碼。

存取修飾子

Kotlin提供private、internal、protected與public關鍵字,可以使用在宣告類別、建構式、介面與函式的宣告,讓應用程式的架構設計更完整,讓物件在運作的時候保持資料的正確性。

在應用程式套件裡面宣告的類別與函式,可以依照應用程式的架構,使用下列的關鍵字:

  • private:只能在宣告的檔案中使用
  • internal:只能在同一個模組中使用
  • public:沒有使用private或internal的時候,預設為public,可以在應用程式任何地方使用

在類別與介面中宣告的屬性、建構式與函式,可以使用下面的關鍵字:

  • private:只能在宣告的類別或介面中使用
  • protected:比private多了可以在子類別或子介面中使用
  • internal:只能在同一個模組中使用
  • public:沒有使用private、protected或internal的時候,預設為public,可以在應用程式任何地方使用

下面是一個使用存取修飾子的範例程式:

/* net.macdidi5.kotlin.tutorial.ch17.Visibility.kt */

package net.macdidi5.kotlin.tutorial.ch17

// 沒有使用private或internal,預設為public
fun defaultPublicFunction() = "Default public"

public fun explicitPublicFunction() = "Explicit public"

internal fun internalFunction() = "Internal"

private fun privateFunction() = "Private"

// 建構式沒有使用private或internal,預設為public
open class Visibility(_value : Int) {

    // 沒有使用private、protected或internal,預設為public
    val defaultPublicProperty = _value
    public val explicitPublicProperty = _value
    internal val internalProperty = _value
    protected val protectedProperty = _value
    private val privateProperty = _value

    fun privateDemo() {
        println("${privateFunction()}, $privateProperty")
    }

}

依照應用程式的需求使用存取修飾子以後,就必須依照規定使用,否則會產生編譯錯誤:

/* net.macdidi5.kotlin.tutorial.ch17.test.VisibilityDemo01.kt */

package net.macdidi5.kotlin.tutorial.ch17.test

import net.macdidi5.kotlin.tutorial.ch17.Visibility
import net.macdidi5.kotlin.tutorial.ch17.defaultPublicFunction
import net.macdidi5.kotlin.tutorial.ch17.explicitPublicFunction
import net.macdidi5.kotlin.tutorial.ch17.internalFunction

// 編譯錯誤,不可以import private函式
// import net.macdidi5.kotlin.tutorial.ch17.privateFunction

import java.util.Random

// 繼承Visibility的子類別
class SubVisibility : Visibility(1) {
    fun demo() {
        println("defaultPublicProperty: $defaultPublicProperty")
        println("explicitPublicProperty: $explicitPublicProperty")
        println("internalProperty: $internalProperty")

        // 可以使用Visibility定義為protected的屬性與函式
        println("protectedProperty: $protectedProperty")

        // 編譯錯誤,不可以使用private屬性
        // println("privateProperty: $privateProperty")
    }
}

// 把建構式定義為private
class PrivateConstructor private constructor() {

    companion object {
        fun game() = if (Random().nextInt(100) > 50) "Win" else "Lose"
    }

}

fun main(args: Array<String>) {

    println(defaultPublicFunction())
    println(explicitPublicFunction())
    println(internalFunction())
    // 編譯錯誤,
    // println(privateFunction())

    val v = Visibility(3)

    println("defaultPublicProperty: ${v.defaultPublicProperty}")
    println("explicitPublicProperty: ${v.explicitPublicProperty}")
    println("internalProperty: ${v.internalProperty}")
    // 編譯錯誤,不可以使用protected屬性
    // println("protectedProperty: ${v.protectedProperty}")
    // 編譯錯誤,不可以使用private屬性
    // println("privateProperty: ${v.privateProperty}")

    // 編譯錯誤,不可以呼叫private constructor
    // val pc = PrivateConstructor()

    println(PrivateConstructor.game())

}

匿名、巢狀與內部與類別

如果一個類別只會在特定的類別中使用,應用程式架構為了執行更好的封裝,可以把它宣告在一個類別裡面。根據類別的用途,可以分為匿名、巢狀與內部類別。如果需要一個繼承類別或實作介面的物件,可以使用匿名類別執行宣告與建立物件,匿名類別可以存取外層類別宣告的屬性與函式:

/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */

package net.macdidi5.kotlin.tutorial.ch17

class Outer {

    private val number = 3

    // 宣告匿名類別
    val anony = object : Any() {

        val number = 5

        override fun toString() : String {
            // 匿名類別宣告的number
            println(number)
            // 匿名類別可以使用Outer類別的屬性與函式
            // Outer宣告的number
            println(this@Outer.number)

            return "Anonymous.number: $number, Outer.number: ${this@Outer.number}"
        }
    }

    ...

}

fun main(args: Array<String>) {
    val outer = Outer()
    println(outer.anony)
}

如果需要宣告一個獨立的類別,這個類別執行的工作,不需要存取外層類別的屬性與函式,可以在類別裡面宣告一個巢狀類別:

/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */

package net.macdidi5.kotlin.tutorial.ch17

class Outer {

    private val number = 3

    ...

    // 宣告巢狀類別
    class Nested {

        private val number = 7

        fun showNumber() {
            println("Nested.number: $number")

            // 不可以使用Outer類別的屬性與函式
            println("can't access this@Outer.number")
        }

    }

    ...

}

fun main(args: Array<String>) {
    val nested : Outer.Nested = Outer.Nested()
    nested.showNumber()
}

如果需要宣告一個獨立的類別,這個類別執行的工作,需要存取外層類別的屬性與函式,可以在類別裡面宣告一個內部類別:

/* net.macdidi5.kotlin.tutorial.ch17.Outer.kt */

package net.macdidi5.kotlin.tutorial.ch17

class Outer {

    private val number = 3

    ...

    // 宣告內部類別
    // 多了inner關鍵字,就可以使用Outer類別的屬性與函式
    inner class Inner {

        private val number = 9

        fun showNumber() {
            // Inner宣告的number
            println(number)
            // Inner宣告的number
            println(this@Inner.number)
            // Outer宣告的number
            println(this@Outer.number)
        }

    }

}

fun main(args: Array<String>) {
    val inner : Outer.Inner = Outer().Inner()
    inner.showNumber()
}

資料類別(Data Class)

應用程式通常需要一些基本的類別(Domain class),用來封裝應用程式需要的物件,如果基本類別沒有特別的需求,可以使用Kotlin提供的
資料類別宣告這些基本類別。下面是一個資料類別的範例:

/* net.macdidi5.kotlin.tutorial.ch17.Item01.kt */

package net.macdidi5.kotlin.tutorial.ch17

// 宣告資料類別
// 包含編號、標題與內容三個屬性
data class Item01(val id: Long,
                  var title: String,
                  var content: String)

Kotlin會自動為資料類別加入下列的函式:

  • equals()與hashCode():判斷物件相等與傳回hash code函式,
  • toString():把屬性資料轉換為字串。
  • componentN():產生與屬性宣告順序對應的讀取函式,可以使用在屬性解構(Destructuring),例如資料類別宣告三個屬性,Kotling會自動產生componnent1()、componnent2()與componnent3(),分別傳回對應的屬性值。
  • copy():複製原有物件的屬性值,傳回一個新的物件

下面是一個使用資料類別的範例程式:

/* net.macdidi5.kotlin.tutorial.ch17.DataClassDemo01.kt */

package net.macdidi5.kotlin.tutorial.ch17

fun main(args: Array<String>) {

    val item01 = Item01(101, "Hello", "Kotlin")
    val item02 = Item01(101, "Hello", "Kotlin")

    println(item01)
    // 顯示: Item01(id=101, title=Hello, content=Kotlin)
    println(item01.toString())
    // 顯示: Item01(id=101, title=Hello, content=Kotlin)

    println(item01.hashCode())
    // 顯示: 116288980
    println(item02.hashCode())
    // 顯示: 116288980

    println(item01.equals(item02))
    // 顯示: true
    println(item01 == item02)
    // 顯示: true

    // data class自動加入的componentN函式
    // 傳回宣告順序的屬性值
    val id01 = item01.component1()
    val title01 = item01.component2()
    val content01 = item01.component3()

    println("$id01, $title01, $content01")
    // 顯示: 101, Hello, Kotlin

    // 使用解構(Destructuring)讀取屬性值
    // 宣告三個變數,依照順序指定componentN函式的回傳值
    val (id, title, content) = item01
    println("$id, $title, $content")
    // 顯示: 101, Hello, Kotlin

    // 複製一個新的物件
    val item03 = item01.copy()
    println(item03)
    // 顯示: Item01(id=101, title=Hello, content=Kotlin)

    // 指定部份的屬性複製一個新的物件
    val item04 = item01.copy(id = 104)
    println(item04)
    // 顯示: Item01(id=104, title=Hello, content=Kotlin)

}

下面的範例程式可以看到資料類別自動產生的函式內容:

/* net.macdidi5.kotlin.tutorial.ch17.generate.Item01.kt */

package net.macdidi5.kotlin.tutorial.ch17.generate

class Item01(val id: Long,
             var title: String,
             var content: String) {

    // 判斷物件是否一樣
    override fun equals(other: Any?): Boolean {
        // 如果物件參考值一樣
        if (this == other) return true
        // 如果參數不是ItemData
        if (other !is Item01) return false

        // 判斷編號、標題與內容
        if (id != other.id) return false
        if (title != other.title) return false
        if (content != other.content) return false

        return true
    }

    // 傳回物件hash code
    override fun hashCode(): Int {
        var result = id.hashCode()
        result = 31 * result + title.hashCode()
        result = 31 * result + content.hashCode()
        return result
    }

    // 轉換為字串
    override fun toString(): String {
        return "net.macdidi5.kotlin.tutorial.ch17.generate.Item01(id=$id, title='$title', content='$content')"
    }


    // 依照屬性宣告的順序傳回屬性值
    operator fun component1() = id
    operator fun component2() = title
    operator fun component3() = content

    // 複製並傳回一個新的物件
    fun copy(id : Long = this.id,
             title : String = this.title,
             content : String = this.content)
        = Item01(id, title, content)

}

fun main(args: Array<String>) {
    val item = Item01(101, "Hello", "Kotlin")
    // 呼叫toString函式
    println(item)
    // 顯示: Item01(id=101, title='Hello', content='Kotlin')

    // 解構函式
    val (id, title, content) = item
    println("$id, $title, $content")
    // 顯示: 101, Hello, Kotlin

    // 複製一個新的物件
    val item02 = item.copy()
    println(item02)
    // 顯示: Item01(id=101, title='Hello', content='Kotlin')

    // 指定部份的屬性複製一個新的物件
    val item03 = item.copy(content = "Java")
    println(item03)
    // 顯示: Item01(id=101, title='Hello', content='Java')

}

Kotlin的資料類別可以依照自己的需求,覆寫toString、equals與hashCode函式,componentN與copy函式「不可以」覆寫。下面的範例程式示範在資料類別加入額外的宣告:

/* net.macdidi5.kotlin.tutorial.ch17.Item02.kt */

package net.macdidi5.kotlin.tutorial.ch17

// 宣告資料類別
data class Item02(val id: Long,
                  var title: String = "",
                  var content: String = "") {

    // 覆寫toString函式
    override fun toString(): String =
        "$id\t$title\t$content"

    // 宣告其它函式
    var details : String = toString()
        // 禁止使用setter
        private set
        // 宣告getter
        get() {
            return "Item02: ${toString()}"
        }

}

下面的範例程式使用上面的資料類別執行一些測式:

/* net.macdidi5.kotlin.tutorial.ch17.DataClassDemo02.kt */

package net.macdidi5.kotlin.tutorial.ch17

fun main(args: Array<String>) {

    val item01 = Item02(101, "Hello", "Kotlin")
    println(item01)
    // 顯示: 101  Hello   Kotlin

    val item02 = Item02(102)
    println(item02)
    // 顯示: 102

    item02.title = "Hi"
    item02.content = "Android"
    println(item02)
    // 顯示: 102  Hi  Android

    println(item02.details)

}

下一步

不論Java或Kotlin程式語言,泛型(generic)都是一個很重要的特性,接下來準備認識泛型的設計與應用。

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

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

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

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

相關文章

留言

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