【Kotlin】類和物件

little_fat_sheep發表於2024-04-07

1 前言

​ Kotlin 是物件導向程式語言,與 Java 語言類似,都有類、物件、屬性、建構函式、成員函式,都有封裝、繼承、多型三大特性,不同點如下。

  • Java 有靜態(static)程式碼塊,Kotlin 沒有;
  • Java 有靜態(static)函式,Kotlin 沒有;
  • Java 建構函式名與類名相同,Kotlin 建構函式名為 constructor;
  • Kotlin 有初始化程式碼塊(init),Java 沒有;
  • Kotlin 有主建構函式,Java 沒有。

​ 在包下面右鍵,依次點選【New → Kotlin Class/File】,輸入類名後,建立 Kotlin 類檔案。

img

​ 如下,建立了一個 Student.kt 檔案。

package com.zhyan8.kotlinStudy

class Student {
}

​ 筆者為簡化程式碼,將定義的類與 main 函式放在同一個檔案中了。

2 類的結構

​ 如下,Student 類是一個自定義的類,裡面包含了一個類的基本結構。

fun main() {
    var stu1 = Student()
    stu1.study()
    println("-----------------------------------------")
    var stu2 = Student("li si", 23)
}

class Student {
    private var name: String = "zhang san" // 屬性
        get() { // name的getter函式
            return field
        }
        set(value) { // name的setter函式
            field = value
        }

    private var age: Int = 18 // 屬性

    init { // 初始化程式碼塊, 在建構函式前執行
        println("Student init, name=$name, age=$age")
    }

    constructor() { // 無參建構函式
        println("create-1, name=$name, age=$age")
    }

    constructor(name: String, age: Int) { // 有參建構函式
        println("create-2, name=$name, age=$age")
        this.name = name
        this.age = age
    }

    fun study() { // 成員函式
        println("study...")
    }
}

​ 說明:init 程式碼塊可以有多個,按照從前往後的順序執行;上述建構函式都是次要建構函式,第 3 節中會介紹主建構函式。

​ 執行程式後,列印如下。

Student init, name=zhang san, age=18
create-1, name=zhang san, age=18
study...
-----------------------------------------
Student init, name=zhang san, age=18
create-2, name=li si, age=23

3 主建構函式

​ 主建構函式是緊接在類名後面的建構函式,次要建構函式是類體內部定義的建構函式,它們的區別如下。

  • 主建構函式:主建構函式只能存在一個,只有函式宣告,沒有函式體,可以在入參中定義類的屬性,會自動進行類屬性的初始化賦值。
  • 次要建構函式:次要建構函式可以存在多個,可以自定義函式體,也可以無函式體,不能在入參中定義類屬性,當類有主建構函式時,所有次要建構函式必須直接或間接地呼叫主建構函式。

3.1 無參主建構函式

fun main() {
    var stu1 = Student()
    println("-----------------------------------------")
    var stu2 = Student("zhang san")
}

class Student() { // 等價與: class Student constructor()
    init { // 初始化程式碼塊, 在建構函式前執行
        println("init")
    }

    constructor(name: String): this() {
        println("constructor, name=$name")
    }
}

​ 執行程式後,列印如下。

init
-----------------------------------------
init
constructor, name=zhang san

​ class Student() 等價於 class Student constructor(),如果需要對主建構函式的許可權進行控制,可以修改如下。

class Student private constructor() {
    ...
}

3.2 有參主建構函式(普通引數)

fun main() {
    var stu1 = Student("xiao ming", 23)
    println("-----------------------------------------")
    // stu1.name // 編譯報錯, name不是成員屬性
    var stu2 = Student()
}

class Student(name: String, age: Int) {
    init {
        println("init, name=$name, age=$age")
    }

    constructor(): this("zhang san", 18) {
        println("constructor")
    }
}

​ 執行程式後,列印如下。

init, name=xiao ming, age=23
-----------------------------------------
init, name=zhang san, age=18
constructor

3.3 有參主建構函式(成員屬性)

fun main() {
    var stu1 = Student("xiao ming", 23)
    println("stu1.name=${stu1.name}, stu1.age=${stu1.age}")
    println("-----------------------------------------")
    var stu2 = Student()
    println("stu2.name=${stu2.name}, stu2.age=${stu2.age}")
}

class Student(var name: String, var age: Int) {
    init {
        println("init, name=$name, age=$age")
    }

    constructor(): this("zhang san", 18) {
        println("constructor")
    }
}

​ 說明:在主建構函式中,透過給入參新增 var(變數)或 val(常量)修飾,使得引數變為成員屬性;在次要建構函式中,不能給入參新增 var 或 val 修飾。

​ 執行程式後,列印如下。

init, name=xiao ming, age=23
stu1.name=xiao ming, stu1.age=23
-----------------------------------------
init, name=zhang san, age=18
constructor
stu2.name=zhang san, stu2.age=18

​ 如果使用者想修改入參屬性的許可權,可以在 var 或 val 前面新增許可權修飾符。

class Student(private val name: String, protected var age: Int) {
    ...
}

4 封裝

​ 封裝是指將相關聯的屬性和函式封裝到同一個類中,並且可以控制這些屬性和函式的訪問許可權,它透過隱藏內部細節和提供清晰的介面,提高了程式碼的安全性、可維護性和可理解性,它是物件導向程式設計中的重要概念之一。

​ 在 Kotlin 中,有四種訪問許可權修飾符:private、protected、internal 和 public。這些修飾符控制了程式碼中類、函式、屬性等成員的可見性和訪問許可權。

  • private:最嚴格的訪問許可權,只在宣告它的類或檔案內可見。
  • protected:與 Java 中的 protected 類似,不同之處在於 Kotlin 中 protected 修飾的成員僅對其子類可見,但不一定在同一個檔案中可見。另外,protected 在 Kotlin 中不能直接應用於頂層函式和屬性(直接定義在檔案中的函式和屬性,而不是在類中定義的)。
  • internal:模組內可見(模組是編譯在一起的一組 Kotlin 檔案),internal 修飾的成員對於同一模組中的任何其他程式碼都是可見的,但對於其他模組中的程式碼是不可見的。
  • public:最寬鬆的訪問許可權,public 成員可以被任何地方的程式碼訪問,如果沒有指定訪問修飾符,預設為 public。

5 繼承

​ 繼承是指一個類(稱為子類或派生類)基於另一個類(稱為父類或基類)建立新類,子類繼承了父類的屬性和函式,並且可以在此基礎上進行擴充套件或修改,它是物件導向程式設計中的重要概念之一。在 Kotlin 中,繼承使用冒號(:)來表示,Any 類是所有類的基類。

​ 類的初始化順序如下。

  1. 父類主建構函式
  2. 父類 init 程式碼塊
  3. 父類次要建構函式
  4. 子類主建構函式
  5. 子類 init 程式碼塊
  6. 子類次要建構函式

5.1 子類無主建構函式

fun main() {
    var stu = Student("zhang san", 23, 1001)
}

open class People(var name: String) {
    init {
        println("People init, name=$name") // 1
    }

    constructor(name: String, age: Int): this(name) {
        println("People constructor, name=$name, age=$age") // 2
    }
}

class Student : People {
    init {
        println("Student init, name=$name") // 3 (此處不能訪問age和id)
    }

    constructor(name: String, age: Int, id: Int) : super(name, age) {
        println("Student constructor, name=$name, age=$age, id=$id") // 4
    }
}

​ 說明:子類必須直接或間接呼叫一下父類的一個建構函式,否則編譯報錯。

​ 執行程式後,列印如下。

People init, name=zhang san
People constructor, name=zhang san, age=23
Student init, name=zhang san
Student constructor, name=zhang san, age=23, id=1001

5.2 子類有主建構函式

fun main() {
    var stu = Student("zhang san", 23, 1001)
}

open class People(var name: String) {
    init {
        println("People init, name=$name") // 1
    }

    constructor(name: String, age: Int): this(name) {
        println("People constructor, name=$name, age=$age") // 2
    }
}

class Student(name: String, var age: Int) : People(name, age) {
    init {
        println("Student init, name=$name, age=$age") // 3 (此處不能訪問id)
    }

    constructor(name: String, age: Int, id: Int): this(name, age) {
        println("Student constructor, name=$name, age=$age, id=$id") // 4
    }
}

​ 說明:子類必須直接或間接呼叫一下父類的一個建構函式,否則編譯報錯;當子類中有主建構函式時,子類中的次要建構函式。

​ 執行程式後,列印如下。

People init, name=zhang san
People constructor, name=zhang san, age=23
Student init, name=zhang san, age=23
Student constructor, name=zhang san, age=23, id=1001

6 多型

​ 多型是指同一個函式可以在不同的物件上表現出不同的行為,這種行為通常透過繼承和介面來實現。多型使得程式碼更加靈活和可擴充套件,是物件導向程式設計中的重要概念之一。

6.1 覆蓋函式

fun main() {
    var peo: People = Student("li si", 25, 1002)
    peo.say()
}

open class People(var name: String, var age: Int) {
    init {
        println("People init, name=$name, age=$age")
    }

    open fun say() {
        println("People say")
    }
}

class Student(name: String, age: Int, var id: Int) : People(name, age) {
    init {
        println("Student init, name=$name, age=$age, id=$id")
    }

    override fun say() {
        println("Student say")
    }
}

​ 執行程式後,列印如下。

People init, name=li si, age=25
Student init, name=li si, age=25, id=1002
Student say

6.2 覆蓋屬性

fun main() {
    var peo : People = Student()
    peo.doSomething()
}

open class People {
    open var name: String = "zhang san"

    fun doSomething() {
        println("doSomething, name=$name")
    }
}

class Student : People() {
    override var name: String = "li si"
}

​ 執行程式後,列印如下。

doSomething, name=li si

6.3 型別智慧轉換

fun main() {
    var peo: People = Student()
    // peo.study() // 編譯報錯
    if (peo is Student) {
        peo.study() // 智慧轉換為Student
    }
}

open class People {
}

class Student : People() {
    fun study() {
        println("study...")
    }
}

​ 說明:Java 沒有智慧轉換特性,需要進行強制型別轉換。

7 抽象類

​ 使用 abstract 修飾的類稱為抽象類,抽象類中可以有抽象屬性和函式,這些屬性和函式被新增了 abstract 修飾符,父類不能實現,子類必須重寫實現(子類如果也是抽象類除外)。抽象類不能被例項化,只能例項化其具化子類,抽象類中允許有具化的屬性和函式。

fun main() {
    // var peo = People() // 編譯報錯, 抽象類不能被例項化
    var stu = Student()
    stu.say()
}

abstract class People {
    abstract var name: String
    abstract fun say()
}

class Student : People() {
    override var name: String = "xiao min"

    override fun say() {
        println("$name: Hello")
    }
}

​ 說明:Java 中只有抽象函式,沒有抽象屬性。

​ 執行程式後,列印如下。

xiao min: Hello

8 介面

​ 介面與抽象類有些類似,介面裡只有抽象屬性和函式(函式允許有預設實現,屬性不能),Kotlin 中允許一個類實現多個介面,但最多隻能繼承一個類。

fun main() {
    var c = C("xxx", "yyy")
    c.aFun()
    c.bFun()
}

interface A {
    var x: String
    fun aFun()
}

interface B {
    var y: String
    fun bFun()
}

class C(override var x: String, override var y: String) : A, B {
    override fun aFun() {
        println("aFun, x=$x")
    }

    override fun bFun() {
        println("bFun, y=$y")
    }
}

​ 執行程式後,列印如下。

aFun, x=xxx
bFun, y=yyy

9 列舉型別

1)列舉類

enum class Color(val tag: String) {
    RED("red") {
        override fun test() {
            println("test, $tag")
        }
    },
    GREEN("green") {
        override fun test() {
            println("test, $tag")
        }
    },
    BLUE("blue") {
        override fun test() {
            println("test, $tag")
        }
    };

    fun printColor(): Unit {
        println("color=$tag")
    }

    abstract fun test()
}

2)enumValueOf、enumValues

fun main() {
    var color: Color = enumValueOf<Color>("RED")
    var colors: Array<Color> = enumValues<Color>()
    println(colors.joinToString()) // RED, GREEN, BLUE
}

3)name、ordinal、entries、values

fun main() {
    println(Color.GREEN.name) // GREEN
    println(Color.RED.ordinal) // 0
    var entries: EnumEntries<Color> = Color.entries
    println(entries) // [RED, GREEN, BLUE]
    var colors: Array<Color> = Color.values()
    println(colors.joinToString()) // RED, GREEN, BLUE
}

4)自定義屬性和函式

fun main() {
    Color.RED.printColor() // color=red
    Color.GREEN.test() // test, green
    println(Color.BLUE.tag) // blue
}

10 data class

​ 在 class 前面新增 data 關鍵字表示為一個資料類,編譯器會根據主建構函式中宣告的所有屬性自動為其生成以下函式。

  • equals()
  • hashCode()
  • toString()
  • componentN()
  • copy()
fun main() {
    val stu1 = Student("Zhang", 20)
    val stu2 = Student("Zhang", 20)
    // hashCode、equals
    println(stu1 == stu2) // true
    // toString
    println(stu1) // Student(name=Zhang, age=20)
    // componentN
    var (name, age) = stu1
    println("($name, $age)") // (Zhang, 20)
    // copy
    var stu3 = stu1.copy()
}

data class Student(var name: String, var age: Int)

​ 為了確保生成程式碼的一致性和有效性,資料類必須滿足以下要求。

  • 主建構函式中至少有一個引數。
  • 主建構函式中的引數必須標記為 val 或 var。
  • 資料類不能是抽象的、開放的、封閉的、內部的。

​ 資料類中成員函式的生成遵循以下規則。

  • 如果資料類中,equals、hashCode、toString 等函式存在顯示實現,或者在父類中有 final 實現,則不會自動生成這些函式,並使用現有的實現。
  • 如果父類具有 open、componentN 函式,並返回相容型別,則為資料類生成相應的函式,並覆蓋父類的相應函式。
  • 不允許為 componentN 和 copy 函式提供顯示實現。

​ 宣告:本文轉自【Kotlin】類和物件

相關文章