2018年8月30日 星期四

Kotlin 的 object 及 class

Kotlin 用關鍵字 object {  } 直接實體化物件,我們可以用無名的(annonumous)物件( object )區塊指令(λ), 實體化後指定給變數, 無名靜態物件只能放在函式區域(local)裏面, 不能放在整體區域(Global):
      fun main(args:  Array < String > ) {
             val myObj = object  {
                           var x: Int = 10
                           fun printx() { println("myObj.x = $x") }
                          // ...
              }
             myObj.printx()
      }
另一種是帶名 object 宣告方式,它不能有建構函式,也不能放在函式區域內, 只能放在整體區域(global)或是類別(class)區塊內,宣告後,實體便已經產生,類似 static object (靜態物件),所以之後就可以直接存取 :
      object myObj {
             var x: Int = 10
             fun printx() { println("myObj.x = $x") }
            // ...
      }
      fun main(args: Array < String > ) {
              myObj.printx()
      }
還有一種是放在類別(class)區塊內的協同物件 companion object { } 宣告, Kotlin會將協同物件與類別名稱綁在一起,類別與物件(object)不同的地方在於:類別必須用建構式 ( ) 實體化(簡稱分類)之後才能存取裡面的成員,但在類別裡面,加了協同物件宣告後, {  } 裡的物件已經與類別綁在一起有了實體(類似靜態物件),因此可以直接用類別名稱存取裏面的成員:
     class myClass {
           companion object myObj {
                var x: Int = 10
                fun printx() { println("myObj.x = $x") }
               // ...          
          }
          // ...
     }
    fun main(args: Array  < String >  ) {
         myClass.myObj.printx( )                       // simply  myClass.printx( ) is th same function
         println("myClass.x is " + myClass.x ) //  myObj is companion object bind to myClass
     }

類別宣告時在類別名稱之後引用冒號 : 接著類別建構式初始繼承程序,此種方式稱為帶名類別繼承,Kotlin 的類別必須在 class 前面加修飾詞 open 才可以開放繼承程序,而其所屬成員也需用修飾詞 open 之後才能讓繼承者重指定加以覆蓋(override), 否則預設都是 final 無法被繼承或覆蓋.
     open class 爺爺 {  // 爺爺可以生, open class
           open var 財產=10 // 可以被覆蓋
           val  生命 = "死掉就沒了"
     }
    open class 爸爸: 爺爺( ) {// 爸爸是爺爺生的, 也可以再生兒女 , 承先可啟後
          override  var 財產=1000
          open var  事業 ="成立新公司" // 可以被覆蓋
    }
    class 兒子: 爸爸( ) { // 兒子是爸爸生的,但沒有 open 就是絕種了(final class)
          override  var 財產=1000000000
          override  var 事業 ="成立連鎖公司"
    }
    fun main(args: Array  < String >  ) {
         var son=兒子( )   
         println(son.財產)
         println(son.事業)
         println(son.生命)
    }

Kotlin 允許使用多個類型建構式實現多重繼承,不同類型建構式之間使用逗號加以分開,但每個繼承者最多只能有一個主要類型(super class), 加上多個介面類型(interface, 是一種開放的可繼承類型, 但不能直接用於實例化)的繼承.另一個比較複雜的無名物件繼承類型,同樣引用冒號當成繼承符號.它的語法是  object : 繼承類型建構式,當然繼承類型必須是開放類型(open class),而且 object: 同樣用逗後將多重類型一一分開加以繼承, .
     open class 爺爺 {  // 爺爺 是祖先類型
           open var 財產=10   // 開放可以覆蓋
           val  生命 = "死掉就沒了"
     }   
    open class 爸爸: 爺爺( ) {    // 爸爸 是帶名繼承類型
          override  var 財產=1000  // 覆蓋爺爺的財產
          open var  事業 ="成立新公司" // 開放可以覆蓋
    }
    fun main(args: Array  < String >  ) {
         var son=object: 爸爸( ) {   // son 是無名物件類型: 從爸爸類型的繼承程序所產生的物件
              override  var 事業 ="青出於藍" // 覆蓋爸爸的事業
         }  
         println(son.財產)
         println(son.事業)
         println(son.生命)
    }
介面類型(interface)可以看成是一個不需要建構式的抽象類型(abstract class), 它本身預設就是開放 open 的類型, 同樣可利用 object: 繼承語法, 先繼承介面類型並實作方法再具體(實例)化成物件, 該物件經由代理條款 by 物件的方式(delegation), 就能讓介面類型用於繼承, 而 class 除非宣告成 open class, 否則預設是內含建構式的 final 類型無法被繼承, 只需透過建構式就可以產生實例(instance), 另外如果單純把類型當作資料庫使用, 可以用 data class (資料類型)把參數和屬性(包括內部特性和方法)建構合而為一的類型, 以下程式碼可以看出 class,  interface,  data class 的差異:
class   Normalclass  {  // 內含建構式 Normalclass( ) 的一般類型
       val state = 3          //  property: 成員特性可初始化狀態
       fun print( ) { println("I come from class. $state") } // method: 函數成員可以計算結果
}
interface IFclass  { // 介面類型: 沒有建構式的抽象類型, 也就是只有屬性
       val state: Int    // 介面的特性(property)不能初始化, 只要資料型態列出就好
       fun print( )       // 介面的方法(method)不用實現, 只要方法型態列出來就好
}
data class  Hello(val sr: String="Sr.")  { //  資料類型: 將參數與屬性合為一,  sr 是屬性之一
       val prefix = "Hi"  // 資料類型的特性
       fun print()   {   println("$prefix $sr.") } // 資料類型的方法
}
fun main() {
    val objectIF = object: IFclass { // 介面類型用 object: 繼承語法,要實現並具體化成物件
           override val state = 5 // 實作時才設定
           override fun print( ) { println("I come from interface. $state") }  // 實現函數
    }
    class Subclass: IFclass by objectIF // 介面類型透過代理條款 by 物件,用於繼承產生新類型
    objectIF.print( )            // 物件 objectIF 不用後綴加上小括號( )
    Subclass( ).print( )        // 一般類型Subclass實例化要用建構式小括號 Subclass( )產生物件
    Normalclass( ).print( ) // 一般類型透過建構式 Normalclass( ) 直接實例化物件
    Hello("Hohn").print( )// data class 也是透過建構式 Hello( ) 直接實例化物件
}
結論:
1.只有一般類型 class (包含 open class 及 final class) 可以透過建構式產生實例形成物件,其它抽象類型(abstract class) 或介面類型(interface)兩者預設雖然都是開放類型可以被繼承, 但不可以直接實例化(instance)物件, 抽象(abstract)與(vs)介面(interface)類型還有一些不同點:
(a). 介面成員的屬性無記憶狀態(state),也不能有建構式, 但抽象類型可以.
(b). 介面類型可以透過特別條款(by clause)經由已實例化物件的代理(delegation)實現繼承衍生新類型(subclass). 但抽象類型不行.
2.預設的 class 類型是 final class,除非宣告成 open class 才可以被繼承衍生新類型(subclass)
3. object 類型建構式 { }  是直接利用類型的建構式去實例化物件
4. object: 介面類型 { } 是先繼承介面類型才實例化成物件
5.一些專有名詞:
    attribute 額外資訊, additional information, 除共通特性外, 另外定義的名稱
    property 類型內特性(characteristic), 泛指所定義的變數名稱, 包括函數名稱及常數名稱
    method  類型內共通方法, 特指所定義的函數名稱 function name, 需經過( )運算才有結果
    class 類型, 完整屬性的體型, 但有名無實, 跑完建構式後就會有實體(instance)
    abstract class 抽象類型, 有初步概念所形成的概觀體型, 完整體型還需進一步實作
    interface 介面類型, 保證無建構式的抽象類型, 同樣有概觀體型, 也需進一步實作
    instance 實例, 是由類型勾鉤出來的物件副本, 常與物件搞混, 實際上搞混也是理所當然.
    object 實體物件,可用句點語法操作本身的特性(properties)或方法(methods)
    object (物件)範疇較大, instance (實例)是某種類型的物件   

沒有留言: