Kotlin 類與繼承

kotlin 中使用關鍵字 class 聲明類

類聲明由類名,類頭(指定其類型參數,主構造函數等),花括號包圍的類體構成,類頭 和類體都是可選的。

class Student {
    
}

如果一個類沒有類體,則可以省略花括號

class Empty 

主構造函數

一個類可以有一個主構造函數和一個或多個次構造函數,主構造函數是類頭的一部分,主構造函數跟在 類名後。

class 類名 constructor(形參1,形參2,形參3{
    
}
class Employee constructor(firstName: String, lastName: String, age: Int) {
    private var name: String
    private var firstName: String
    private var lastName: String
    private var workTime: Int

    init {
        println("first init")
        this.firstName = firstName   // 主構造函數不能包含任何初始化代碼,所以放在 init 中初始化成員變量 firstName,lastName
        this.lastName = lastName
        this.name = this.firstName + this.lastName    // 成員變量可以在 init 初始化代碼塊中初始化
        workTime = age / 2
    }

    init {
        println("second init")
    }
}

// 輸出結果:
first init
second init

注意點:

  • 關鍵字 constructor ,在 Java 中,構造方法名必須和類名相同,而在 kotlin 中,是通過 contructor 關鍵字來標明的,且對於主構造函數而言,它的位置是在 類頭(即緊跟類名的後面),而不是在類體中。
  • 關鍵字 init : init 被稱作是初始化代碼塊,他的作用是爲主構造函數服務的,由於主構造函數是放在 類頭中的,是不能包含任何初始化語句的(這是語法規定的),這個時候就是 init 的用武之地了,我們可以把初始化執行語句放在 init 代碼塊中進行初始化,爲類的成員屬性賦值。
  • 在實例初始化期間,初始化代碼塊按照他們出現在類體重的順序執行,與屬性初始化器交織在一起。

如果主構造函數沒有任何註解或可見性修飾符,則可以省略這個 constructor 關鍵字。

class Employee(fristName: String, lastName: String, age: Int) { // 省略 constructor 關鍵字

}

類的成員屬性初始化不是必須放在 init 代碼塊中的,可以在定義屬性時直接將主構造函數中的形參賦值給他。

class Employee constructor(firstName: String, lastName: String, age: Int) {
    private var name: String = firstName + lastName
    private var firstName: String = firstName
    private var lastName: String = lastName
    private var workTime: Int = age / 2

    init {
        println("first init")
        this.firstName = firstName   // 主構造函數不能包含任何初始化代碼,所以放在 init 中初始化成員變量 firstName,lastName
        this.lastName = lastName
        this.name = this.firstName + this.lastName    // 成員變量可以在 init 初始化代碼塊中初始化
        workTime = age / 2
    }

    init {
        println("second init")
    }
}

可以發現,這種在構造函數中聲明形參,然後在屬性定義進行賦值,有點繁瑣,我們可以直接在主構造函數中定義類的屬性。

class Computer(private var name: String, private var price: Int) {

    fun printInfo() {
        println("name = $name , price  = $price")
    }
}
fun main(args: Array<String>) {
    var appleComputer = Computer("apple", 10000)
	appleComputer.printInfo()
}

輸出結果:

name = apple , price  = 10000

當我們在定義一個類時,如果沒有顯式的提供主構造函數,Kotlin 編譯器會默認生成一個無參主構造函數,這點和 Java 是一樣的

class Mouse {
    private var name: String = "LenovoMousc"
    private var price: Int = 23
    fun printInfo() {
        println("name = $name , price = $price")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse()
    lenovoMouse.printInfo()
}

輸出結果:

name = LenovoMousc , price = 23

次構造函數

與主構造函數不同的是: 次構造函數是定義在類體中的,並且次構造函數可以有多個,而主構造函數只能有一個。

class MyButton : AppCompatButton {
    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.buttonStyle)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

注意點:

  • 可以使用 this 關鍵字來調用 自己的其他構造函數
  • 使用 super 關鍵字來調用父類構造函數

我們看下,同時定義主構造函數和多個次構造函數

class Mouse(name: String, price: Int, address: String) {

    private var name: String = name
    private var price: Int = price
    private var address: String = address

    constructor(name: String) : this(name, 0)

    constructor(name: String, price: Int) : this(name, price, "")

    fun printInfo() {
        println("name = $name , price  = $price, address = $address")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse("applemouse")
    lenovoMouse.printInfo()
}

一個 三個參數的主構造函數,兩個次構造函數。一個參數的次構造函數調用了兩個參數的次構造函數,兩個參數的次構造函數調用了三個參數的主構造函數。

我們把 兩個參數的次構造函數的 this(name,price,"") 刪除掉,會出現什麼問題呢?

class Mouse(name: String, price: Int, address: String) {

    private var name: String = name
    private var price: Int = price
    private var address: String = address

    constructor(name: String) : this(name, 0)

    constructor(name: String, price: Int) {}    // 刪除掉調用三個參數的主構造器,會提示: Primary constructor call expected

    fun printInfo() {
        println("name = $name , price  = $price, address = $address")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse("applemouse")
    lenovoMouse.printInfo()
}

刪除三個參數的主構造函數會提示: Primary constructor call expected

結論:次構造函數會直接或間接調用主構造函數的

創建類的實例

要創建一個類的實例,可以普通函數一樣調用 構造函數。

kotlin 中沒有 new 關鍵字

var employee = Employee("1001", "xiaohe", 12)

類成員

類可以包含:

  • 構造函數與初始化代碼塊
  • 函數
  • 屬性
  • 嵌套類和內部類
  • 對象聲明

繼承

在 kotlin 中所有類都有一個共同的超類 Any , 這對於沒有聲明超類型的類提供了默認的超類

注意點:

  • Kotlin 中如果類前面不加關鍵字 open, 則該類默認是 final 的 ,也就是不能被繼承,如果繼承該類,會報錯: This type is final, so it cannot be inherited from.

  • Any 類 默認提供了三個方法 : equal() , hashCode() ,toString()

  • 基類中沒有定義構造器時,Kotlin 會默認添加一個無參的構造器,所以子類在繼承該基類時,需要實現該基類的無參構造器。

    // 編譯器會提供的默認無參的構造器
    open class Phone {
    }
    
    // 繼承基類時,需要實現基類中的無參的構造器
    class ApplePhone(private var rom: Int) : Phone() {
    
        fun printInfo() {
            println("the rom is $rom")
        }
    }
    

    輸出結果:

    the rom is 4
    
  • 在寫子類是,如果子類中有主構造函數,那麼基類必須要在主構造函數中立即初始化

    // open 標識該類可以被繼承
    open class Mouse(name: String, price: Int, address: String) {
        private var name: String = name
        private var price: Int = price
        private var address: String = address
        
        constructor(name: String) : this(name, 0)
    
        constructor(name: String, price: Int) : this(name, price, "")
    
        fun printInfo() {
            println("name = $name , price  = $price, address = $address")
        }
    }
    
    class AppleMouse(name: String, price: Int, address: String, wireless: Boolean) : Mouse(name, price, address) {
    
    }
    
  • 如果子類中沒有主構造函數,那麼必須要在此構造函數中調用 super 關鍵字初始化父類,或者調用另一個此構造函數。初始化父類時,可以調用父類的不同的構造函數。

重寫

重寫方法

在繼承基類時,子類可以重寫基類中的有 open 關鍵字修飾的方法,如果沒有 open 修飾的方法,是允許子類重寫的,重寫方法時,需要使用 override 關鍵字標識。

在 IDEA IDE 中,在子類中可以使用快捷鍵 Ctrl + O 快速重寫父類中被 open 關鍵字修飾的方法

// 基類有默認的無參構造器,所以子類繼承時,
open class Phone {
    open fun getColor() {
        println("phone has many colors")
    }
}

class ApplePhone(private var rom: Int) : Phone() {

    override fun getColor() {
        super.getColor()
        println("iPone color is red")
    }

    fun printInfo() {
        println("the rom is $rom")
    }
}

fun main(args:Array<String>) {
    var iPhone = ApplePhone(4)
    iPhone.getColor()
}

輸出結果:

iPone color is red

如果一個類繼承了多個類或接口,並且父類或接口中有相同的名字的方法需要重寫,那麼子類這時候必須重寫該方法,並且如果子類想區分父類中的方法,可以使用 super 關鍵字調用不同的父類方法。

/**
 * 類中的 open 關鍵字標識該類可以被繼承
 */
open class Fly {
    /**
     * 方法中的 open 關鍵字標識該方法可以被重寫
     */
    open fun show() {
        println("Fly ......")
    }
}

/**
 * interface 不用寫 open, 默認就是 open 的
 */
interface Run {
    /**
     * 接口中的方法默認是 open 的
     */
    fun show() {
        println("Run .....")
    }
}

/**
 * 子類實現接口時不用寫 (), 因爲接口是沒有構造函數的
 */
class Bird : Fly(), Run {
    override fun show() {
        super<Fly>.show()   // 使用 super 關鍵字來調用不同父類中的同名方法
        super<Run>.show()
    }
}

輸出結果:

Fly ......
Run .....
重寫變量
  • 基類中被重寫的變量也要有 open 關鍵字的聲明,
  • 子類可以使用 var 類型的變量去重寫父類中 val 類型的變量,但是不能使用 val 類型的變量去重寫父類中 var 類型的變量(如果使用 val 類型的變量去重寫父類的 var 類型的變量,那麼子類這個 val 類型的變量就會多一個 set 方法,而 val 類型的變量是不允許有 set 方法的)
open class Car(maxSpeed: Int, price: Int) {
    open val maxSpeed: Int = maxSpeed
    open var price: Int = price
}

class Maserati(maxSpeed: Int, price: Int) : Car(maxSpeed, price) {
    /**
     * 子類使用 var 類型重寫父類中的 val 類型的變量
     */
    override var maxSpeed: Int = maxSpeed
    /**
     * 子類使用 val 類型重寫父類中的 var 類型的變量,========>>>>>>編譯報錯
     */
    override val price: Int = price
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章