iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Software Development

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

[Day 15] Kotlin Journey - object, companion object

  • 分享至 

  • xImage
  •  

Nested Class v.s Inner Class

Nested Class (巢狀類別)和 Inner Class (內部類別) 兩者好像很類似,但其實不大一樣。

Nested Class (巢狀類別)

Nested Class 如下,也就是 class 包了 class,但要注意下列幾點

  • 外 class 和 內 class 是獨立的,所以內部 class 是無法訪問外部 class 的屬性
  • 因為內外 class 各自獨立,所以要實例化內部 class 要使用 Outer.Nested()
class Outer {
    val outer = "outer property"
    class Nested {
        val nested = " nested property"
        fun print() {
            // println(outer) // 無法存取到外部的 outer 
            println(nested)
        }
    }
}

fun main() {
    val nestedd = Outer()
    nestedd.outer

    val nested = Outer.Nested() // 實例化內部 class
    nested.print()
}

下圖就是顯示了無法存取外部 class 的屬性

https://ithelp.ithome.com.tw/upload/images/20200924/20129902JZ0QR9WvHu.png

如果只實例化外部 class,也存取不到內部的 class,這再次說明彼此是獨立的。

https://ithelp.ithome.com.tw/upload/images/20200924/201299021BrXHfdkBv.png

Inner Class (內部類別)

Inner Class 就跟巢狀類別不一樣了,內部 class 只要加上 inner 關鍵字,內部 class 就會成為外部 class 的一員,所以也就能存取到外部 class 的屬性,所以重點如下

  • Inner Class 是外部 class 的一員,可以存取到外部 class 的屬性
  • 實例化內部 class 時,要先建立外部 class val outer = Outer() 才能建立內部 class, val inner = outer.Inner()
class Outer {
    val outer = "outer property"
    
    inner class Inner {
        val inner = " inner property"
        fun print() {
            println(outer) // 可以存取到外部的 outer
            println(inner)
        }
    }
}

fun main() {
    
    // inner class 
    val outer = Outer()
    val inner = outer.Inner()
    inner.print()
}

但值得注意的是 inner class 會有造成 memory leak 的風險,請小心使用!在以往 Java 的經驗來說,其實不大會使用 inner class。

有興趣可以參考此篇:Understanding Memory Leaks in Java(https://www.baeldung.com/java-memory-leaks)

object

Singleton Object

object 是 Kotlin 很方便的一個關鍵字,他會直接幫你創造實體化一個 thread safe 的 Singleton Object

下面的例子,我舉了一個像是序號產生器的例子,這裡不使用 class 宣告,直接使用 object 宣告這是一個 Singleton 物件。

object SerialNoGenerator {
    var count = 0;
    fun gen(): Int {
        count++
        println(count)
        return count
    }
}

在 main 中就可以直接用 class name 來呼叫方法 SerialNoGenerator.gen(),不需要自己建立物件。

fun main() {
    SerialNoGenerator.gen()
    SerialNoGenerator.gen()
    SerialNoGenerator.gen()
    SerialNoGenerator.gen()
}

結果:
1
2
3
4

沒有 constructor(),有 init block

也由於會自動幫我們建立物件,所以沒有 constructor(),如果要做初始化或檢查的事,可以使用 init block

object SerialNoGenerator {
    var count = 0
    init {
        // do init...
    }
    fun gen(): Int {
        count++
        println(count)
        return count
    }
}

可以繼承其他類別

object 的類別可以繼承其他 class,如下,如此 SerialNoGenerator 就可以使用父類別的 printGeneratorNo()

open class Generator {
    fun printGeneratorNo(no: Int) {
        println(no)
    }
}

object SerialNoGenerator : Generator() {
    var count = 0

    init {
        // do init...
    }

    fun gen(): Int {
        count++
        printGeneratorNo(count)
        return count
    }
}

object 看起來很像 static member,但不全然是

因為不需要自己創立物件,直接這樣呼叫就好 SerialNoGenerator.gen()

使得看起來很像靜態呼叫方法 (static invoke),但其實 Kotlin 並沒有 static 的關鍵字存在,當然去查看 byte code,底層實作 singleton object 也是 static 的,但 Kotlin 在這 object 的重點是他幫你創造了一個實體,記得這個就好了

https://ithelp.ithome.com.tw/upload/images/20200924/20129902ImIL5Y8jou.png

object expression

object expression 就像是匿名類別的實作

如下例

多宣告了一個 BaseClass class,都是 open 代表可以繼承和覆寫方法。

open class BaseClass {
    open fun getParameter(): String {
        return "parameters"
    }
}

open class Generator {
    fun printGeneratorNo(no: Int) {
        val parameter = object : BaseClass() {
            override fun getParameter() = "Generator's parameter"
        }

        println("parameter: ${parameter.getParameter()}")
        println("no: $no")
    }
}

在 Generator 內, 可以直接使用 object expression 直接實做和實例化出這個物件,然後使用。

        val parameter = object : BaseClass() {
            override fun getParameter() = "Generator's parameter"
        }

最後就可以直接呼叫

        println("parameter: ${parameter.getParameter()}")

Companion Object (伴生物件)

  • Companion Object 定義在 class 內的 singleton object
  • 在 object 前面加上 companion 前綴關鍵字
  • 在寫 Companion Object 時可以不用取名稱

Companion Object 主要有兩個作用

  • 代替靜態成員
  • Factory Pattern 工廠模式

代替靜態成員

在 SerialNoGeneratorK 內宣告 companion object {} 其中的 count 和 gen() 就會有類似靜態屬性和方法的效果。

class SerialNoGeneratorK {

    companion object {
        var count = 0
        fun gen(): Int {
            count++
            println(count)
            return count
        }
    }
}

fun main() {
    SerialNoGeneratorK.gen()
    SerialNoGeneratorK.gen()
    SerialNoGeneratorK.gen()
    SerialNoGeneratorK.gen()

Factory Pattern 工廠模式

如之前 Day 11 有提到的,private constructor 就是用在這時候

這裡使用 companion object 達到像工廠模式的方式,只透過 create() 來建立物件,而不能透過 class 的 constructor(被宣告成 private 了)

class Entity private constructor(val someData: String) {
    companion object Factory{
        fun create(): Entity {
            return Entity("someData")
        }
    }
}

最後創造物件時可以有 companion object 的名稱 Entity.Factory.create(),也可以不用名稱 Entity.create()

fun main() {
    val entity = Entity.Factory.create()
    println(entity)
    val entity2 = Entity.create()
    println(entity2)

}

以上就是今天的內容!我們明天見!

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


上一篇
[Day 14] Kotlin Journey - Abstract class & Interface (抽象類別和介面)
下一篇
[Day 16] Kotlin Journey - Data Class
系列文
Kotlin 島深度之旅 30 天31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言