練習:Kotlin 基礎知識

1. 事前準備

您已花了很多心力瞭解 Kotlin 程式設計的基本概念,現在是時候來實際運用所學了。

這些練習可測試您對於所學概念的理解程度,由於內容是根據實際生活中的案例所研擬,某些情境或許您之前便已經歷過。

請按照 Kotlin Playground 中的操作說明,為每項練習尋找解決方案。如果遇到窒礙難行之處,可以從某些練習中尋找提示。每項練習的結尾處都會提供解決方案程式碼,但建議您先自行練習解決問題再查看答案。

請依照自己的步調逐一完成練習。每項練習都有預估完成時間,但這僅供參考,您不必一定要在時間內完成。請花些時間仔細思考每個問題的解決方法。我們提供的解決方案只是一種解決問題的方式,您還可以多方嘗試、盡情實驗各種方法。

必要條件

軟硬體需求

  • Kotlin Playground

2. 行動裝置通知

一般來說,您的手機會提供通知摘要。

在以下程式碼片段提供的初始程式碼中,您將根據收到的通知數量,撰寫可輸出摘要訊息的程式。訊息應包含以下資訊:

  • 收到少於 100 則通知時,顯示確切通知數量。
  • 收到超過 100 則通知時,顯示 99+ 則通知數量。
fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    // Fill in the code.
}

完成 printNotificationSummary() 函式,讓程式輸出以下幾行內容:

You have 51 notifications.
Your phone is blowing up! You have 99+ notifications.

3. 電影票價格

電影票價格通常會根據觀看者年齡而不同。

在程式碼片段的初始程式碼中,根據以下條件編寫按年齡計算票價的程式:

  • 12 歲以下的兒童票價為 $15 美元。
  • 13 至 60 歲間的標準票價為 $30 美元,但每週一這個年齡層可享折扣票價 $25 美元。
  • 61 歲以上的長者票價為 $20 美元,而我們假設電影觀看者最高年齡為 100 歲。
  • 使用 -1 值代表使用者輸入的年齡在指定年齡範圍以外。
fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    // Fill in the code.
}

完成 ticketPrice() 函式,讓程式輸出以下幾行內容:

The movie ticket price for a person aged 5 is $15.
The movie ticket price for a person aged 28 is $25.
The movie ticket price for a person aged 87 is $20.

4. 溫度轉換器

目前全球有三種主要溫度標準,分別為攝氏、華氏和克氏。

在程式碼片段的初始程式碼中,根據以下公式編寫可在不同溫度標準之間轉換的程式:

  • 攝氏至華氏:° F = 9/5 (° C) + 32
  • 克氏至攝氏:° C = K - 273.15
  • 華氏至克氏:K = 5/9 (° F - 32) + 273.15

請注意,String.format("%.2f", /* measurement */ ) 方法可用來將數字轉換為具有 2 位小數點的 String 類型。

fun main() {
    // Fill in the code.
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

完成 main() 函式,讓該函式呼叫 printFinalTemperature() 函式並顯示下列幾行內容。您需要傳遞溫度和轉換公式的引數。提示:建議您使用 Double 值,以免 Integer 在除法運算期間遭截斷。

27.0 degrees Celsius is 80.60 degrees Fahrenheit.
350.0 degrees Kelvin is 76.85 degrees Celsius.
10.0 degrees Fahrenheit is 260.93 degrees Kelvin.

5. 歌曲目錄

假設您需要建立音樂播放器應用程式。

您將建立可代表歌曲結構的類別。Song 類別必須包含以下程式碼元素:

  • 歌名、演出者、出版年份和播放次數的屬性
  • 表示歌曲是否熱門的屬性。如果播放次數少於 1,000,可將歌曲視為不熱門。
  • 可輸出歌曲描述的方法,格式如下:

「[歌名],演出者為 [演出者],於 [出版年份] 發行。」

6. 網際網路個人資料

您往往必須在線上網站上填寫個人資料,其中包含必填和非必填欄位。舉例來說,您可以新增個人資訊,並將這份資訊連結至推薦您註冊個人資料的其他使用者。

在以下程式碼片段提供的初始程式碼中,您將編寫可輸出使用者個人詳細資料的程式。

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
       // Fill in code
    }
}

完成 showProfile() 函式,讓程式輸出以下幾行內容:

Name: Amanda
Age: 33
Likes to play tennis. Doesn't have a referrer.

Name: Atiqah
Age: 28
Likes to climb. Has a referrer named Amanda, who likes to play tennis.

7. 折疊式手機

一般來說,按下電源鍵時,手機螢幕就會開啟或關閉。相反地,如果在折疊狀態的折疊式手機上按下電源鍵時,主要的內部螢幕不會開啟。

在以下程式碼片段提供的初始程式碼中,您將編寫從 Phone 類別沿用的 FoldablePhone 類別,其中應包含下列項目:

  • 表示手機是否為折疊狀態的屬性。
  • Phone 類別運作方式不同的 switchOn() 函式,讓手機在展開狀態時才會開啟螢幕。
  • 可變更折疊狀態的方法。
class Phone(var isScreenLightOn: Boolean = false){
    fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if (isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

8. 特別拍賣

一般來說,拍賣中出價最高者會決定某個商品的價格。在這類特別拍賣活動中,如果某個商品沒有人出價,則會以最低價格自動賣給拍賣行。

在以下程式碼片段提供的初始程式碼中,您將取得接受可為空值的 Bid? 類型做為引數的 auctionPrice() 函式:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
   // Fill in the code.
}

完成 auctionPrice() 函式,讓程式輸出以下幾行內容:

Item A is sold at 5000.
Item B is sold at 3000.

9. 解決方案程式碼

行動裝置通知

解決方案會使用 if/else 陳述式,根據收到的通知訊息數量輸出適當的通知摘要訊息:

fun main() {
    val morningNotification = 51
    val eveningNotification = 135

    printNotificationSummary(morningNotification)
    printNotificationSummary(eveningNotification)
}

fun printNotificationSummary(numberOfMessages: Int) {
    if (numberOfMessages < 100) {
        println("You have ${numberOfMessages} notifications.")
    } else {
        println("Your phone is blowing up! You have 99+ notifications.")
    }
}

電影票價格

解決方案會使用 when 運算式,根據電影觀看者的年齡傳回適當的票價。此外,也會針對 when 運算式的其中一個分支版本,使用簡易 if/else 運算式新增標準票價的額外條件。

如果 else 分支版本中的票價傳回 -1 值,就表示 else 分支版本設定的價格無效。更理想的實作方式是讓 else 分支版本擲回例外狀況。後續學習單元會說明例外狀況的處理方法。

fun main() {
    val child = 5
    val adult = 28
    val senior = 87

    val isMonday = true

    println("The movie ticket price for a person aged $child is \$${ticketPrice(child, isMonday)}.")
    println("The movie ticket price for a person aged $adult is \$${ticketPrice(adult, isMonday)}.")
    println("The movie ticket price for a person aged $senior is \$${ticketPrice(senior, isMonday)}.")
}

fun ticketPrice(age: Int, isMonday: Boolean): Int {
    return when(age) {
        in 0..12 -> 15
        in 13..60 -> if (isMonday) 25 else 30
        in 61..100 -> 20
        else -> -1
    }
}

溫度轉換器

解決方案會要求您將函式做為參數傳遞至 printFinalTemperature() 函式。最簡潔的解決方案是將 lambda 運算式做為引數傳遞,以 it 參數參照取代參數名稱,並使用結尾 lambda 語法。

fun main() {
        printFinalTemperature(27.0, "Celsius", "Fahrenheit") { 9.0 / 5.0 * it + 32 }
        printFinalTemperature(350.0, "Kelvin", "Celsius") { it - 273.15 }
        printFinalTemperature(10.0, "Fahrenheit", "Kelvin") { 5.0 / 9.0 * (it - 32) + 273.15 }
}

fun printFinalTemperature(
    initialMeasurement: Double,
    initialUnit: String,
    finalUnit: String,
    conversionFormula: (Double) -> Double
) {
    val finalMeasurement = String.format("%.2f", conversionFormula(initialMeasurement)) // two decimal places
    println("$initialMeasurement degrees $initialUnit is $finalMeasurement degrees $finalUnit.")
}

歌曲目錄

解決方案包含 Song 類別,內含可接受所有必要參數的預設建構函式。Song 類別也包含使用自訂 getter 函式的 isPopular 屬性,以及輸出本身說明的方法。您可以在 main() 函式中建立該類別的例項,並呼叫其方法來測試實作是否正確。編寫數值較大的數字 (例如 1_000_000 值) 時,您可以使用底線讓數字便於閱讀。

fun main() {
    val brunoSong = Song("We Don't Talk About Bruno", "Encanto Cast", 2022, 1_000_000)
    brunoSong.printDescription()
    println(brunoSong.isPopular)
}

class Song(
    val title: String,
    val artist: String,
    val yearPublished: Int,
    val playCount: Int
){
    val isPopular: Boolean
        get() = playCount >= 1000

    fun printDescription() {
        println("$title, performed by $artist, was released in $yearPublished.")
    }
}

對執行個體的方法呼叫 println() 函式時,程式可能會輸出以下內容:

We Don't Talk About Bruno, performed by Encanto Cast, was released in 2022.
true

網際網路設定檔

這項解決方案會在不同的 if/else 陳述式中加入空值檢查,根據各種類別屬性是否為 null 的情形顯示不同文字:

fun main() {
    val amanda = Person("Amanda", 33, "play tennis", null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}

class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
        println("Name: $name")
        println("Age: $age")
        if(hobby != null) {
            print("Likes to $hobby. ")
        }
        if(referrer != null) {
            print("Has a referrer named ${referrer.name}")
            if(referrer.hobby != null) {
                print(", who likes to ${referrer.hobby}.")
            } else {
                print(".")
            }
        } else {
            print("Doesn't have a referrer.")
        }
        print("\n\n")
    }
}

折疊式手機

如要讓 Phone 類別成為父項類別,您需要在類別名稱前面加上 open 關鍵字,將該類別設為開放式類別。如要覆寫 FoldablePhone 類別中的 switchOn() 方法,必須在方法前面加上 open 關鍵字,將 Phone 類別中的方法變更為開放式方法。

解決方案包含 FoldablePhone 類別,內含可接受 isFolded 參數預設引數的預設建構函式。FoldablePhone 類別也有兩種方法,可將 isFolded 屬性變更為 truefalse 值,也會覆寫從 Phone 類別沿用的 switchOn() 方法。

您可以在 main() 函式中建立該類別的例項,並呼叫其方法來測試實作是否正確。

open class Phone(var isScreenLightOn: Boolean = false){
    open fun switchOn() {
        isScreenLightOn = true
    }

    fun switchOff() {
        isScreenLightOn = false
    }

    fun checkPhoneScreenLight() {
        val phoneScreenLight = if (isScreenLightOn) "on" else "off"
        println("The phone screen's light is $phoneScreenLight.")
    }
}

class FoldablePhone(var isFolded: Boolean = true): Phone() {
    override fun switchOn() {
        if (!isFolded) {
            isScreenLightOn = true
        }
    }

    fun fold() {
        isFolded = true
    }

    fun unfold() {
        isFolded = false
    }
}

fun main() {
    val newFoldablePhone = FoldablePhone()

    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
    newFoldablePhone.unfold()
    newFoldablePhone.switchOn()
    newFoldablePhone.checkPhoneScreenLight()
}

輸出內容如下:

The phone screen's light is off.
The phone screen's light is on.

特別拍賣

這項解決方案會使用安全呼叫運算子 ?. 和 Elvis 運算子 ?:,傳回正確價格:

fun main() {
    val winningBid = Bid(5000, "Private Collector")

    println("Item A is sold at ${auctionPrice(winningBid, 2000)}.")
    println("Item B is sold at ${auctionPrice(null, 3000)}.")
}

class Bid(val amount: Int, val bidder: String)

fun auctionPrice(bid: Bid?, minimumPrice: Int): Int {
    return bid?.amount ?: minimumPrice
}

10. 其他練習

如果想取得更多有關 Kotlin 語言的練習內容,請查看 JetBrains Academy 的 Kotlin 基本概念課程。如要跳到特定主題,請前往知識地圖,瀏覽課程所涵蓋主題的清單。