2019年7月28日 星期日

理解 Kotlin 的 companion object (變身) vs instance of class (化身) 之不同

在 Kotlin 中可以將類型與類型建構式分別指定給變數, 到底有何不同, 看以下例子:
class AAA { // 類型 AAA,  預設的建構式就是 AAA( )
    var b = 3 // 這是待化身的變數宣告, 透過建構式 AAA( ) 化身成物件成員(instance member)
    companion object {// 這是協同物件, 也就是 AAA 的變身, 唯一實體共享物件(static object)
        var a  = 5  // 需透過 AAA 存取成員
        var b = 10 // 名字同化身的成員, 直接變身成物件成員, 必需透過 AAA 存取成員
    }
}
fun main(args: Array < String > ) {
    val c = AAA    // c 是 AAA 的變身(companion object), 但不是化身(instance of class)
    val i  = AAA( ) // i 才是類型的化身, 透過類型建構式 ( ) 才能化身成物件(instance of class)
    println(c.a)    // a 是 AAA 共享成員
    println(c.b)    // b 也是 AAA 成員之一
    println(i@AAA.a) // i 要透過變身標籤 @AAA 才能存取共享成員 a
    println(i@AAA.b) // i 要透過變身標籤 @AAA 才能存取共享成員 b
    // println(i.a)  //  錯誤的用法: a 實際並非 i 的成員
    // println(i.b)  //  錯誤的用法: b 實際並非 i 的成員
    println(i.b)     //  這裡的 b 是透過建構式生成的, 並非變身(companion object)裡面的 b
執行結果:
5
10
5
10
3
變身與化身中文意義是相同的, 如果把英文的 companion object(協同物件)翻譯成"變身", 而將 instance of class (類型物件)翻譯成"化身"時, 兩者本質上都是物件, 就要區分它們的不同, 也許可以這樣理解: 一旦宣告了協同物件(簡稱變身), 類型名稱可以變身(ailias of companion object)成物件去存取共享成員, 而類型物件(instance of class)必需透過建構式去化身, 兩者物件的生成方式不同在於: 變身在是在編譯階段(compile phase)生成的靜態物件(static object),從程式開始執行到結束生命都一直維係著不會消失, 但化身是在執行階段(runtime)才生成的區域物件(scope object), 生命周期受到控制, 一旦離開所在的生命區域(scope)便消失殆盡. 變身主要目的是維繫共享資源, 實際上只有一份變身物件存在類型內,  所有類型的化身並沒有變身這個物件!如果真要在化身物件方便取用,除了用 @標籤外, 也可以用 set() 及 get() 刻意連結起來,只不過要注意的是, 因為是共享的(static object),若在多工環境下也許要用 semaphore 來加以鎖定.
class AAA { // 類型 AAA,  預設化身的建構式就是 AAA( )
    companion object {// 這是協同物件, 也就是 AAA 的變身, 唯一實體共享物件(static object)
          var a  = 5  //  AAA 變身的成員
          var b = 10 // 若名稱與化身的成員相同時, 必需透過 AAA 存取成員
    }
    var b: Int  get( )         {  return AAA.b } // 符號讀取連結, 讓化身間接讀取變身成員 b
                     set(v: Int) {  AAA.b = v      } // 符號寫入連結, 讓化身間接寫入變身成員 b
    fun a( ) { // 當化身成員名稱與變身的成員不同時, 化身可以取用到變身的成員只是因為內定(default)符號聯結的關係, 並不表示他就是化身的成員
         return a  // 等同取用 AAA.a , 只是因為 kotlin 內定會將符號 a 聯結到變身成員 AAA.a
    }
}
fun main(args: Array < String > ) {
    val c = AAA    // c 是 AAA 的變身(companion object), 但不是化身(instance of class)
    val i  = AAA( ) // i 是類型的化身, 透過類型建構式 ( ) 化身成物件(instance of class)
    println(c.a)    // a 是 AAA 共享成員
    println(c.b)    // b 也是 AAA 成員之一
    println(i.a())  // 呼叫函式 a( )間接讀取 AAA.a
    println(i.b)    // 已經刻意將 b 做了符號讀取連結, 看似直接(實際是間接)讀取
    i.b = 3           // 已經刻意將 b 做了符號寫入連結, 看似直接(實際是間接)寫入
    println(c.b)    // 因為 b 是共享成員, 所以變身的成員也跟著改變
     
}
結果:
5
10
5
10
3

沒有留言: