iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Software Development

Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app系列 第 6

從 Class 到多型——一次掌握 Kotlin 物件導向程式設計

  • 分享至 

  • xImage
  •  

一次掌握 Kotlin 物件導向程式設計

昨天我們認識了 Array、List、Set、Map 等集合類型,學會如何管理多筆資料。今天要進入 Kotlin 非常重要的「物件導向程式設計」,從類別、物件、屬性、方法開始,一路介紹 getter/setter、建構子、繼承、方法覆寫、向上轉型與多型。參考資料:Kotlin Classes

今天使用的課堂範例:

專案程式碼總目錄:Kotlin/0616/KT_3/src

這些連結使用你指定的 commit 8e6d0e5986e77c2a354935663126c973de621bc1,因此之後即使 main 分支內容改變,文章仍會連到這一版程式碼。參考資料:GitHub—About commits


一、什麼是類別與物件?

類別(Class)可以看成建立物件的設計圖,裡面定義物件擁有哪些資料,以及能夠執行哪些操作。根據類別建立出來的實體,稱為物件(Object)或執行個體(Instance)。參考資料:Kotlin Classes

以遊戲玩家為例,每一位玩家都有姓名與分數,也能執行顯示資料的動作,因此可以建立一個 PlayerClass 類別:

class PlayerClass {

    // 玩家姓名屬性。
    var name: String = ""

    // 玩家分數屬性。
    var score: Int = 0

    // 顯示玩家目前的姓名與分數。
    fun show() {
        println("$name,score = $score")
    }

    // 修改玩家資料。
    // newScore 有預設值,因此呼叫時可以只修改姓名。
    fun setData(
        newName: String,
        newScore: Int = score
    ) {
        name = newName
        score = newScore
    }
}

fun main() {

    // 使用 PlayerClass() 建立第一個玩家物件。
    val player1 = PlayerClass()

    player1.name = "Mary"
    player1.score = 100
    player1.show()

    // 建立第二個獨立的玩家物件。
    val player2 = PlayerClass()

    player2.name = "John"
    player2.score = 80
    player2.show()

    // 只傳入姓名,分數會保留原本的 80。
    player2.setData(newName = "Jack")
    player2.show()

    // 同時修改姓名與分數。
    player2.setData(
        newName = "Jane",
        newScore = 88
    )
    player2.show()
}

執行結果:

Mary,score = 100
John,score = 80
Jack,score = 80
Jane,score = 88

player1player2 雖然都由 PlayerClass 建立,但它們是兩個不同物件,各自擁有自己的 namescore。修改其中一個物件,不會影響另一個物件。參考資料:Kotlin Classes


二、屬性與方法

類別裡通常包含兩種主要成員:屬性(Property)與方法(Method)。屬性負責保存物件狀態,例如學生姓名、年齡及成績;方法則負責執行操作,例如判斷是否及格或列印學生資料。參考資料:Kotlin PropertiesKotlin Functions

以下是完整的學生類別:

class Student {

    // 學生姓名。
    var name: String = ""

    // 學生年齡。
    var age: Int = 0

    // 學生成績。
    var score: Int = 0

    // 判斷成績是否達到 60 分。
    fun isPass(): Boolean {
        return score >= 60
    }

    // 顯示學生的完整資料。
    fun printInfo() {
        val passStatus = if (isPass()) "是" else "否"

        println(
            "姓名:$name,年齡:$age,成績:$score,及格:$passStatus"
        )
    }
}

fun main() {

    val student1 = Student()
    student1.name = "Tom"
    student1.age = 18
    student1.score = 85

    val student2 = Student()
    student2.name = "Amy"
    student2.age = 17
    student2.score = 92

    val student3 = Student()
    student3.name = "Bob"
    student3.age = 19
    student3.score = 55

    student1.printInfo()
    student2.printInfo()
    student3.printInfo()
}

執行結果:

姓名:Tom,年齡:18,成績:85,及格:是
姓名:Amy,年齡:17,成績:92,及格:是
姓名:Bob,年齡:19,成績:55,及格:否

isPass() 放進 Student 類別後,及格判斷就成為學生物件本身具備的能力,不需要在主程式裡重複撰寫相同條件。這就是把資料與操作封裝在同一個類別中的基本概念。參考資料:Kotlin Classes


三、使用類別解決矩形計算問題

類別除了保存人物或商品資料,也能封裝計算邏輯。矩形具有寬度與高度,並可以根據這兩項屬性計算面積及周長。參考資料:Kotlin Member Functions

class Rectangle {

    // 矩形寬度。
    var width: Int = 0

    // 矩形高度。
    var height: Int = 0

    // 計算矩形面積。
    fun area(): Int {
        return width * height
    }

    // 計算矩形周長。
    fun perimeter(): Int {
        return (width + height) * 2
    }
}

fun main() {

    val rectangle1 = Rectangle()
    rectangle1.width = 120
    rectangle1.height = 30

    val rectangle2 = Rectangle()
    rectangle2.width = 250
    rectangle2.height = 60

    println("--- 矩形 1 ---")
    println("寬:${rectangle1.width}")
    println("高:${rectangle1.height}")
    println("面積:${rectangle1.area()}")
    println("周長:${rectangle1.perimeter()}")

    println()

    println("--- 矩形 2 ---")
    println("寬:${rectangle2.width}")
    println("高:${rectangle2.height}")
    println("面積:${rectangle2.area()}")
    println("周長:${rectangle2.perimeter()}")
}

執行結果:

--- 矩形 1 ---
寬:120
高:30
面積:3600
周長:300

--- 矩形 2 ---
寬:250
高:60
面積:15000
周長:620

四、自訂 getter 與 setter

Kotlin 的 var 屬性預設具有 getter 與 setter。getter 負責讀取屬性,setter 負責寫入屬性;我們也可以自訂它們,在讀取或寫入資料時進行轉換與驗證。參考資料:Kotlin Getters and Setters

1. 自動轉成大寫

import java.util.Locale

class ToUpper {

    var text: String = ""

        // 寫入資料時進行檢查與轉換。
        set(value) {
            field = if (value.isEmpty()) {
                "No data"
            } else {
                value.uppercase(Locale.getDefault())
            }
        }

        // 讀取資料時加上提示文字。
        get() {
            return "transfer data:$field"
        }
}

fun main() {

    val data = ToUpper()

    data.text = "hello, Mary"
    println(data.text)

    data.text = ""
    println(data.text)
}

執行結果:

transfer data:HELLO, MARY
transfer data:No data

在自訂存取子裡,field 代表屬性真正保存資料的幕後欄位(Backing Field)。如果在 setter 裡再次寫成 text = value,就會不斷呼叫自己而造成遞迴,因此必須使用 field 存取實際內容。參考資料:Kotlin Backing Fields

2. 驗證姓名、編號與電子郵件

class Player {

    // private 代表只能在 Player 類別內直接存取。
    private var phone: String = "123456"

    var name: String = "Mary"
        get() = field.uppercase()
        set(value) {
            // 去除姓名前後的空白。
            field = value.trim()
        }

    var id: Int = 1
        get() = field + 100
        set(value) {
            if (value > 10) {
                println("ID 錯誤:ID 必須小於或等於 10")
                field = 0
            } else {
                field = value
            }
        }

    var email: String = "aaa@gmail.com"
        get() = field.lowercase()
        set(value) {
            if ("@" in value) {
                field = value
            } else {
                println("電子郵件格式錯誤")
            }
        }

    // code 本身不另外保存資料,而是透過 id 進行轉換。
    var code: Int
        get() = id - 100
        set(value) {
            id = value
        }

    // 外部不能直接修改 phone,
    // 因此提供公開方法控制修改流程。
    fun changePhone(newPhone: String) {
        phone = newPhone
    }

    fun printInfo() {
        println(
            "name = $name,id = $id," +
                "email = $email,phone = $phone"
        )
    }
}

fun main() {

    val player = Player()

    player.printInfo()

    player.name = "  John  "
    player.id = 2
    player.email = "John@Example.COM"

    player.printInfo()

    println("code = ${player.code}")

    // 超過允許範圍,setter 會顯示錯誤並將實際值設成 0。
    player.id = 15
    println("id = ${player.id}")

    // 透過公開方法修改 private 屬性。
    player.changePhone("0912-345-678")
    player.printInfo()
}

這個設計展示了封裝(Encapsulation)的概念:物件不必把所有資料完全公開,而是透過 setter 或公開方法限制資料的修改方式,避免物件進入不合理的狀態。參考資料:Kotlin Visibility Modifiers


五、主建構子

建構子(Constructor)用來在建立物件時接收初始資料。寫在類別名稱後面的建構子稱為主建構子(Primary Constructor)。參考資料:Kotlin Constructors

class NewPlayer(
    var name: String,
    var age: Int,
    var healthPoint: Int = 80
) {

    fun printInfo() {
        println(
            "name = $name,age = $age," +
                "healthPoint = $healthPoint"
        )
    }
}

fun main() {

    // 三個參數全部傳入。
    val player1 = NewPlayer(
        name = "John",
        age = 22,
        healthPoint = 90
    )

    player1.printInfo()

    // healthPoint 使用預設值 80。
    val player2 = NewPlayer(
        name = "Mary",
        age = 30
    )

    player2.printInfo()
}

如果主建構子的參數前面加上 varval,Kotlin 會直接將參數宣告成類別屬性。var 代表建立後仍能修改,val 則代表建立後不能重新指定。參考資料:Kotlin Primary Constructor


六、init 初始化區塊

主建構子本身不能直接放置一般程式敘述。如果建立物件時需要執行驗證、轉換或顯示訊息,可以使用 init 初始化區塊。參考資料:Kotlin Initializer Blocks

class GamePlayer(
    playerName: String,
    playerAge: Int,
    blessed: Boolean
) {

    var name: String = playerName
        set(value) {
            if (value.length > 10) {
                println("姓名長度不能超過 10 個字元")
                field = "Error"
            } else {
                field = value
            }
        }

    var age: Int = playerAge

    var isBlessed: Boolean = blessed

    init {
        println("正在建立玩家物件……")
        println("初始玩家:$name")
    }

    fun printInfo() {
        println(
            "name = $name,age = $age," +
                "isBlessed = $isBlessed"
        )
    }
}

fun main() {

    val player = GamePlayer(
        playerName = "Mary",
        playerAge = 18,
        blessed = true
    )

    player.printInfo()

    // 這個姓名超過 10 個字元,會經過 setter 驗證。
    player.name = "Mary123456789"
    player.printInfo()
}

init 會在物件建立時自動執行,而且會依照它在類別中出現的順序,與屬性初始化程式一起執行。參考資料:Kotlin Initializer Blocks


七、次要建構子

當類別需要支援多種建立方式時,可以使用 constructor 宣告次要建構子(Secondary Constructor)。每個次要建構子最終都必須直接或間接呼叫主建構子。參考資料:Kotlin Secondary Constructors

以下的 Info 可以只接收姓名與年齡,也能額外接收電子郵件與電話:

class Info(
    name: String,
    age: Int
) {

    var name: String = name

    var age: Int = age

    var email: String = "No Email"

    var phone: String = "No Phone"

    // 第一個次要建構子:
    // 接收姓名、年齡與電子郵件。
    constructor(
        name: String,
        age: Int,
        email: String
    ) : this(name, age) {

        // 電子郵件必須包含 @。
        if ("@" in email) {
            this.email = email
        }
    }

    // 第二個次要建構子:
    // 接收姓名、年齡、電子郵件與電話。
    constructor(
        name: String,
        age: Int,
        email: String,
        phone: String
    ) : this(name, age, email) {

        // 驗證台灣市內電話的簡化格式:4 碼-4 碼。
        val phoneRegex = Regex("^\\d{4}-\\d{4}$")

        if (phoneRegex.matches(phone)) {
            this.phone = phone
        }
    }

    fun printInfo() {
        println(
            "姓名:$name,年齡:$age," +
                "Email:$email,電話:$phone"
        )
    }
}

fun main() {

    // 呼叫主建構子。
    val person1 = Info(
        name = "Mary",
        age = 20
    )
    person1.printInfo()

    // 呼叫第一個次要建構子。
    val person2 = Info(
        name = "John",
        age = 22,
        email = "john123@mail.com"
    )
    person2.printInfo()

    // 呼叫第二個次要建構子。
    val person3 = Info(
        name = "Nancy",
        age = 21,
        email = "nancy@mail.com",
        phone = "2274-5678"
    )
    person3.printInfo()

    // 電話格式不正確,會保留預設值 No Phone。
    val person4 = Info(
        name = "Jack",
        age = 33,
        email = "jack@gmail.com",
        phone = "22222"
    )
    person4.printInfo()
}

執行結果:

姓名:Mary,年齡:20,Email:No Email,電話:No Phone
姓名:John,年齡:22,Email:john123@mail.com,電話:No Phone
姓名:Nancy,年齡:21,Email:nancy@mail.com,電話:2274-5678
姓名:Jack,年齡:33,Email:jack@gmail.com,電話:No Phone

八、另一種次要建構子寫法

類別也可以不宣告主建構子,改為在類別內使用一個或多個 constructor。不過,如果只是設定屬性的初始值,通常使用主建構子會更加精簡。參考資料:Kotlin Constructors

class CarInfo {

    var brand: String
    var model: String
    var year: Int
    var speed: Int

    // 接收品牌、型號與年份的建構子。
    constructor(
        brand: String,
        model: String,
        year: Int
    ) {
        this.brand = brand
        this.model = model
        this.year = year
        this.speed = 0
    }

    // 沒有傳入年份時,呼叫上一個建構子,
    // 並將年份設為 2026。
    constructor(
        brand: String,
        model: String
    ) : this(
        brand = brand,
        model = model,
        year = 2026
    )

    fun accelerate(increase: Int) {
        speed += increase
    }

    fun printInfo() {
        println("Brand:$brand")
        println("Model:$model")
        println("Year:$year")
        println("Speed:$speed")
        println("====================")
    }
}

fun main() {

    val car = CarInfo(
        brand = "BMW",
        model = "X3",
        year = 2025
    )

    car.printInfo()

    car.speed = 50
    car.accelerate(20)
    car.printInfo()

    val newCar = CarInfo(
        brand = "Toyota",
        model = "RAV4"
    )

    newCar.printInfo()
}

九、Kotlin 類別為什麼要加 open?

Kotlin 的類別與成員預設都是不可繼承、不可覆寫的,也就是預設具有類似 Java final 的效果。若要讓其他類別繼承,父類別前面必須加上 open;要允許子類別改寫屬性或方法,也必須在該成員前面加上 open。參考資料:Kotlin Inheritance

open class ParentClass(
    val name: String
) {

    open fun show() {
        println("Parent name:$name")
    }
}

class ChildClass(
    name: String,
    val age: Int
) : ParentClass(name) {

    override fun show() {
        // 呼叫父類別原本的 show()。
        super.show()

        // 加入子類別自己的顯示內容。
        println("Child name:$name,age:$age")
    }
}

fun main() {

    val child = ChildClass(
        name = "John",
        age = 20
    )

    child.show()
}

執行結果:

Parent name:John
Child name:John,age:20

子類別的建構子必須把父類別需要的資料傳給父類別。在 ChildClass 後面的 : ParentClass(name),就是呼叫父類別的建構子。參考資料:Kotlin Derived Class Initialization


十、override 與 super

override 表示子類別要覆寫父類別允許覆寫的屬性或方法;super 則可以存取父類別版本的屬性或方法。參考資料:Kotlin Overriding Methods

open class Game(
    val player: String,
    playerLevel: Int
) {

    open var level: Int = playerLevel

    open val score: Int = 60

    open fun printInfo() {
        println(
            "Game Player:$player," +
                "Level:$level,Score:$score"
        )
    }
}

open class ChildGame(
    player: String,
    playerScore: Int
) : Game(
    player = player,
    playerLevel = 10
) {

    override var level: Int = 50

    override val score: Int = playerScore

    init {
        // 取得父類別初始化後的 level,再加 1。
        level = super.level + 1
    }

    override fun printInfo() {
        // 先執行父類別版本。
        super.printInfo()

        // 再執行子類別新增的內容。
        println(
            "ChildGame Player:$player," +
                "Level:$level,Score:$score"
        )
    }
}

class GrandchildGame(
    player: String,
    playerLevel: Int
) : ChildGame(
    player = player,
    playerScore = 100
) {

    override var level: Int = playerLevel

    override fun printInfo() {
        super.printInfo()

        println(
            "GrandchildGame Player:$player," +
                "Level:$level,Score:$score"
        )
    }
}

十一、向上轉型

把子類別物件放進父類別型別的變數,稱為向上轉型(Upcasting)。因為子類別本來就是父類別的一種,所以 Kotlin 通常可以自動完成向上轉型,不需要額外使用 as。參考資料:Kotlin Type Checks and Casts

fun main() {

    val child = ChildGame(
        player = "Mary",
        playerScore = 88
    )

    // 自動向上轉型,不必寫成 child as Game。
    val game: Game = child

    game.printInfo()
}

雖然 game 變數的型別是 Game,實際保存的物件仍然是 ChildGame,所以呼叫 printInfo() 時,執行的是 ChildGame 覆寫後的方法。這項行為正是多型的基礎。參考資料:Kotlin Inheritance


十二、什麼是多型?

多型(Polymorphism)指的是使用相同的父類別型別操作不同子類別物件,而執行時會依物件的實際型別,自動呼叫正確的覆寫方法。參考資料:Kotlin Inheritance

fun printGameInfo(game: Game) {
    // 實際執行哪一個 printInfo(),
    // 由傳入物件的真正型別決定。
    game.printInfo()
}

fun main() {

    val game1: Game = Game(
        player = "AAA",
        playerLevel = 8
    )

    val game2: Game = ChildGame(
        player = "BBB",
        playerScore = 77
    )

    val game3: Game = GrandchildGame(
        player = "CCC",
        playerLevel = 2
    )

    printGameInfo(game1)
    println()

    printGameInfo(game2)
    println()

    printGameInfo(game3)
}

printGameInfo() 只宣告接收 Game,卻可以處理 GameChildGameGrandchildGame。未來即使新增其他 Game 子類別,這個函式通常也不需要修改,這就是多型對程式擴充性的重要價值。參考資料:Kotlin Inheritance


十三、is 型別判斷與智慧轉型

當父類別變數可能保存不同子類別物件時,可以使用 is 判斷實際型別。判斷成功後,Kotlin 會進行智慧轉型(Smart Cast),因此不必再手動轉型。參考資料:Kotlin is and Smart Casts

open class Person(
    val name: String
) {

    fun show() {
        println("姓名:$name")
    }
}

class StudentPerson(
    name: String,
    val score: Int
) : Person(name) {

    fun showScore() {
        println("姓名:$name,成績:$score")
    }
}

fun checkPerson(person: Person) {

    when (person) {

        // 判斷成功後,person 會智慧轉型成 StudentPerson。
        is StudentPerson -> person.showScore()

        // 其他 Person 物件執行一般顯示。
        else -> person.show()
    }
}

fun main() {

    checkPerson(
        Person(name = "Mary")
    )

    checkPerson(
        StudentPerson(
            name = "John",
            score = 90
        )
    )
}

when 中應優先判斷範圍較小的子類別,再處理父類別或使用 else。否則,如果先判斷父類別,子類別物件也會符合父類別條件,後面的子類別分支便可能無法執行。參考資料:Kotlin Type Checks


十四、汽車繼承實作

接著以汽車為例建立父類別 Car,再讓燃油車與電動車繼承共同資料。品牌、型號及年份放在父類別,燃油里程與充電功能則分別放在子類別。參考資料:Kotlin Inheritance

open class Car(
    val brand: String,
    val model: String,
    val year: Int
) {

    // 加上 open,讓子類別可以依自身特性覆寫。
    open fun displayInfo() {
        println(
            "汽車品牌:$brand,型號:$model,年份:$year"
        )
    }
}

class OilCar(
    brand: String,
    model: String,
    year: Int
) : Car(
    brand = brand,
    model = model,
    year = year
) {

    override fun displayInfo() {
        println(
            "燃油車品牌:$brand,型號:$model,年份:$year"
        )
    }

    fun calculateRange(
        fuelCapacity: Int,
        fuelEfficiency: Int
    ): Int {
        return fuelCapacity * fuelEfficiency
    }
}

class ElectricCar(
    brand: String,
    model: String,
    year: Int,
    initialBattery: Int
) : Car(
    brand = brand,
    model = model,
    year = year
) {

    // 電量限制在 0~100 之間。
    var batteryCapacity: Int =
        initialBattery.coerceIn(0, 100)
        private set

    override fun displayInfo() {
        println(
            "電動車品牌:$brand,型號:$model," +
                "年份:$year,電量:$batteryCapacity%"
        )
    }

    fun charge(amount: Int) {
        // 充電後最高不能超過 100%。
        batteryCapacity =
            (batteryCapacity + amount).coerceAtMost(100)

        println(
            "$brand $model 已充電至 $batteryCapacity%"
        )
    }
}

fun main() {

    val normalCar = Car(
        brand = "Toyota",
        model = "RAV4",
        year = 2022
    )

    val oilCar = OilCar(
        brand = "BMW",
        model = "X3",
        year = 2025
    )

    val electricCar = ElectricCar(
        brand = "Tesla",
        model = "Model 3",
        year = 2026,
        initialBattery = 50
    )

    normalCar.displayInfo()
    println()

    oilCar.displayInfo()

    val drivingRange = oilCar.calculateRange(
        fuelCapacity = 50,
        fuelEfficiency = 15
    )

    println("預估可行駛 $drivingRange 公里")
    println()

    electricCar.displayInfo()
    electricCar.charge(amount = 30)
    electricCar.displayInfo()

    println()
    println("--- 使用多型顯示所有汽車 ---")

    // 父類別集合可以同時保存父類別與不同子類別物件。
    val carList: List<Car> = listOf(
        normalCar,
        oilCar,
        electricCar
    )

    for (car in carList) {
        car.displayInfo()
    }
}

執行結果:

汽車品牌:Toyota,型號:RAV4,年份:2022

燃油車品牌:BMW,型號:X3,年份:2025
預估可行駛 750 公里

電動車品牌:Tesla,型號:Model 3,年份:2026,電量:50%
Tesla Model 3 已充電至 80%
電動車品牌:Tesla,型號:Model 3,年份:2026,電量:80%

--- 使用多型顯示所有汽車 ---
汽車品牌:Toyota,型號:RAV4,年份:2022
燃油車品牌:BMW,型號:X3,年份:2025
電動車品牌:Tesla,型號:Model 3,年份:2026,電量:80%

這裡的 carList 宣告為 List<Car>,卻能同時保存 CarOilCarElectricCar。迴圈每次都呼叫相同的 displayInfo(),但實際顯示結果會依照物件型別改變,完整呈現多型的效果。參考資料:Kotlin Overriding Methods


十五、類別命名注意事項

Kotlin 官方程式碼風格建議類別名稱使用大駝峰式命名法(UpperCamelCase),例如 PlayerClassNewPlayerElectricCar。函式、屬性及區域變數則使用小駝峰式命名法(lowerCamelCase),例如 displayInfo()batteryCapacityplayerName。參考資料:Kotlin Coding Conventions

建議把原始練習中的 newPlayer2newPlayer3 改成 NewPlayer2NewPlayer3,並把 batteryCappacity 修正為 batteryCapacity。程式即使使用原名稱仍可能正常執行,但遵守一致的命名規則能讓程式更容易閱讀與維護。參考資料:Kotlin Coding Conventions


十六、今天的重點整理

今天從最基本的類別與物件開始,逐步學會如何將資料宣告成屬性、將功能封裝成方法,以及如何透過 getter、setter 驗證或轉換資料。參考資料:Kotlin ClassesKotlin Properties

主建構子適合用來簡潔地宣告必要資料,init 可以處理物件建立時的初始化工作,次要建構子則能提供不同的物件建立方式。參考資料:Kotlin Constructors

需要建立父子關係時,父類別及可覆寫成員必須使用 open,子類別使用 override 改寫行為,並能透過 super 呼叫父類別原本的實作。參考資料:Kotlin Inheritance

最後,透過向上轉型與多型,我們可以使用同一個父類別型別管理不同的子類別物件;搭配 is 與智慧轉型,還能安全判斷物件的真正型別。參考資料:Kotlin Type Checks and Casts


十七、課後練習

請建立一個 Employee 父類別,包含姓名與基本薪資,並宣告可覆寫的 calculateSalary() 方法。接著建立 FullTimeEmployeePartTimeEmployee 子類別,讓兩者使用不同方式計算薪資。參考資料:Kotlin Inheritance

最後,把不同員工放進 List<Employee>,使用 for 迴圈依序呼叫 calculateSalary(),觀察同一個方法如何根據不同物件產生不同結果。這就是今天學到的多型實際應用。參考資料:Kotlin Collections OverviewKotlin Inheritance

下一篇將繼續進入 Kotlin 物件導向的重要功能,包括抽象類別(Abstract Class)、介面(Interface),以及如何使用介面降低類別之間的相依性。參考資料:Kotlin Interfaces


上一篇
Kotlin 集合大亂鬥:Array、List、Set、Map,一次搞懂資料怎麼存
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言