【kotlin從入門到深坑】之類和繼承

簡介

本篇博客主要是介紹Kotlin語法中的【類和繼承】相關的知識,幫助各位更快的掌握Kotlin,如果有疏漏或者錯誤,請在留言中指正,謝謝。 系列彙總地址


聲明類

Kotlin中使用關鍵詞class 聲明類和java中保持一致,代碼如下:

class Demo{ //class + 類名

}

類的構成=class +類名+類頭(可選)+類體(可選),如果沒有類體,括號也可省略

class Demo //class + 類名

主構造函數

在Kotlin的類中可以有一個主構造函數和多個次構造函數。我們看看什麼樣的算主構造函數,例子如下:

class Demo(){

}

看了例子估計心裏開始有疑惑了,這和上面的例子有什麼不同??仔細一看還真有不同,多了”()”,這就聲明瞭一個無參的主構造函數,當然你也可以聲明有參數的,如下

class Demo(name: String){

}

主構造函數只是類頭的一部分,還有一個可選關鍵詞–constructor,當主構造函數想聲明爲非public 類型的時候,需要使用該關鍵詞其他時候可以省略,例如:

class Demo private constructor(name: String){

}

這種情況一般是在寫單例的時候會用到。

構造函數都有了,我們可以愉快的做些初始化了,但是但是要注意,主構造函數內不能包含任何代碼。 這個時候我們需要使用另一個關鍵詞 init,使用init 代碼塊進行初始化代碼即可,如下:

class Demo private constructor(name: String) {
    init {//init代碼塊
        println("測試輸出")
    }
}

上面的代碼中name 可以直接使用在 init代碼塊內和類體內聲明的屬性初始化中使用,只有這兩種情況,代碼如下

class Demo (name: String) {
    init {
        println("測試輸出"+name) //在此處使用可以
    }
    var nameTest=name //此處給nameTest賦值可以,但不可以單獨使用
}

當然我們可以讓上面的name 有更多的用途怎麼辦呢?可以這樣寫:

class Demo(var name: String) { //此處加入 var關鍵詞
    init {
        println("測試輸出" + name)
    }

    var nameTest = name

    fun printTest() { //方法內也可使用了
        println(name)
    }
}

不僅可以使用var關鍵詞,還可以使用val,具體的差別之前我們說過,再次不再贅述。
使用該關鍵詞後,name就等同於是該類的成員變量,就等同於你直接聲明在類中。
對於Kotlin來說還有個好用的地方,就是你聲明的類中變量不需要寫get/set方法,默認就會有,可以直接用。

次構造函數

Kotlin中類也可以聲明前綴有 constructor 的次構造函數,如下:

class Demo { //有個默認的無參主構造函數
  constructor(){// 有個無參次構造函數

  }
}

如果類有一個主構造函數,每個次構造函數需要委託給主構造函數, 可以直接委託或者通過別的次構造函數間接委託。委託到同一個類的另一個構造函數 用 this 關鍵字即可

下面根據主構造函數是否重寫來分別講解,先說未重寫:

class A{

}

如何寫它的次構造函數呢?

class A {
    init {
        println("--A主構造--")
    }

    constructor(name: String) { //此處屏蔽了 默認的主構造函數
        println("--A次構造--")

    }
}

上面的寫法會導致Kotlin的默認無參主構造函數不可調用, 也就導致constructor(name: String):this() 失效,但我們上面說過必須要委託給主構造函數,這怎麼辦?其實Kotlin內部仍然會走默認的主構造函數,也就是說如果是默認主構造是可以省略不寫的,有的時候是想寫也沒法寫

得到上面的結論是源於一個測試,代碼如下:

var a = A("name"); //此處已經無法調用無參的,也就是默認的主構造失效,內部使用this() 也是不可以的

後面的打印結果

--A主構造--   //仍然走了主構造函數,此時只有默認主構造函數
--A次構造--   //然後再走次構造

所以得到上面的結論,在此佐證。 後續用實際代碼詳解,此處挖坑

有的筒子可能就會想了,如果我們仍然想要一個無參數的構造函數怎麼寫?

class A {
    init {
        println("--A主構造--")
    }

    constructor(name: String) {
        println("--A次構造--")

    }

    constructor(){ //增加一個無參的次構造函數

    }
}

還有另一個方式,就是重寫主構造函數,這兩種方法都類似 java中自己重寫一個無參的構造函數,現在就是區分了主、次構造函數的重寫。還需強調的是主構造如果重寫了,次構造不允許再重寫。下面寫個重寫主構造函數的,後面細說:

class A() { //重寫此處
    init {
        println("--A主構造--")
    }

    constructor(name: String) : this() {
        println("--A次構造--")

    }

//    constructor(){ //次構造不允許使用
//        
//    }
}

下面我們講一下重寫主構造函數的
如下:

class Demo() { //沒參數的主構造函數
    constructor(name: String) : this() { //次構造函數委託給
    }
}

此處就和默認的不一樣了,這個地方需要使用this(),因爲你重寫了主構造函數。如果不重寫是可以不寫this()

通過上方的各種對比和例子我們對類的構造函數有了大概的瞭解,我們爲了方便理解在這裏總結一波,還有不明白的多看看例子:

  • 類只有一個主構造函數,但可以有一個或多個次構造函數

  • 次構造函數需要委託給主構造函數,可以通過直接或者間接的方式

  • 在聲明期間,以主構造函數爲主,如果主構造函數重寫了對應的構造方法,次構造函數不能用重複的出現對應的構造函數,上面有例子說明。

  • 在調用期間,以次構造函數爲主,如果未重寫主構造函數,默認會有一個無參的主構造函數,如果重寫一個無參次構造函數,則可調用的只有次構造函數。

創建類的實例

要創建一個類的實例,我們就像普通函數一樣調用構造函數:

val invoice = Invoice()
val customer = Customer("Joe Smith")
//注意 Kotlin 並沒有 new 關鍵字。

繼承

在 Kotlin 中所有類都有一個共同的超類 Any,這對於沒有超類型聲明的類是默認超類:

class Example // 從 Any 隱式繼承

Any 不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外沒有任何成員。 更多細節請查閱Java互操作性部分。

要聲明一個顯式的超類型,我們把類型放到類頭的冒號之後:

open class A(p: Int) //注意此處的 open 關鍵詞

class B(p: Int) : Base(p) //使用" :"

對於繼承的類來說,它仍需滿足之前類中主次構造函數的規定,還需滿足繼承類委託給被繼承類的主構造函數(保證對應類的init模塊都能運行的關鍵),此處的被委託的主構造函數如果是無參構造函數,可以省略不寫

下面我們分兩種類型去分別講解,首先是未修改默認的主構造函數的情況:
我們先看一下被繼承類:

open class BB { //未重寫默主構造

    constructor(name: String) {
        println(name + "---BB")
    }


    constructor() {
        println(".....---BB")

    }
}

繼承類:

class testA : BB { //此處testA也沒重寫主構造
    constructor(name: String) : super(name) {//--    1)
        println("A---" + name)
    }

    constructor() {//--  2)
        println("A---" + "ss")
    }
}

位置1處的構造函數,因爲未重寫主構造,所以本類的委託完成,所以只需委託父類即可,即可滿足上面說的要求。

位置2處的構造函數,因爲未重寫主構造,所以本類的委託完成,又因爲默認是委託父類的無參主構造函數(系統默認的或者重寫的都滿足),所以滿足上面說的要求。

我們做下改動,將B 改動如下:

open class BB() {//重寫默認主構造

    constructor(name: String):this() { //手動委託主構造
        println(name + "---BB")
    }


//    constructor() { //根據主次函數關係,此處無法存在
//        println(".....---BB")
//
//    }
}

這時候我們的繼承類如何寫呢?

class testA : BB {
    constructor(name: String) : super(name) {
        println("A---" + name)
    }

    constructor() {
        println("A---" + "ss")
    }
}

可以看到代碼是沒變化的,也就是說,只要是被繼承類的主構造函數,是無參的,無論是默認還是重寫都可以省略不寫,當然也可以寫上

class testA : BB {
    constructor(name: String) : super(name) {//可以選擇不同的父類的主構造函數進行委託
        println("A---" + name)
    }

    constructor():super() {//無參的委託
        println("A---" + "ss")
    }
}

下面我們將一下修改默認主構造函數的情況

class testB(name: String) : BB(name) { //此處testB重寫了默認主構造,所以此處B一定需要用主構造函數(直接或間接的)
    init {
        println(name + "99ss...---B")
    }

    constructor(name: String, age: Int) : this(name) {
        println(name + "...---B")
    }
}

也可以這樣寫:

class testB(name: String) : BB() { //此處必須有"()"
    init {
        println(name + "99ss...---B")
    }

    constructor(name: String, age: Int) : this(name) {  //此處不能使用super了,不能重複委託父類,主構造函數已經委託過了
        println(name + "...---B")
    }
}

下面我們總結一波:

  • 如果繼承的類重寫了默認主構造函數,此時必須用基類的主構造函數初始化(直接或間接)

  • 如果繼承的類沒重寫默認的主構造函數,此時可以使用super關鍵詞初始化,我們也說過,未重寫的的默認主構造函數,次構造函數都會委託給它,所以本類內也滿足了委託條件,對於類外,也委託了被繼承類的主構造方法,完成了兩個類的條件


總結

至此已經學完了Kotlin的類和繼承相關的知識,多回顧多思考,繼續後續內容

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章