1 前言
Kotlin 是物件導向程式語言,與 Java 語言類似,都有類、物件、屬性、建構函式、成員函式,都有封裝、繼承、多型三大特性,不同點如下。
- Java 有靜態(static)程式碼塊,Kotlin 沒有;
- Java 有靜態(static)函式,Kotlin 沒有;
- Java 建構函式名與類名相同,Kotlin 建構函式名為 constructor;
- Kotlin 有初始化程式碼塊(init),Java 沒有;
- Kotlin 有主建構函式,Java 沒有。
在包下面右鍵,依次點選【New → Kotlin Class/File】,輸入類名後,建立 Kotlin 類檔案。
如下,建立了一個 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 類是所有類的基類。
類的初始化順序如下。
- 父類主建構函式
- 父類 init 程式碼塊
- 父類次要建構函式
- 子類主建構函式
- 子類 init 程式碼塊
- 子類次要建構函式
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】類和物件。