练习:Kotlin 基础知识

1. 准备工作

您已经下大力气来学习 Kotlin 编程的基础知识,现在是时候将所学知识付诸实践了。

这里的练习可以测试您对所学概念的理解程度,都是基于实际用例,其中有些用例您可能之前就遇到过。

请按照相关说明在 Kotlin 园地中寻找各项练习的解决方案。如果您遇到困难,某些练习还会给出提示来帮助您解题。各项练习的解决方案代码就在文末,不过建议您先解题,然后再看答案。

您可以按照自己的节奏来完成练习。这些练习都有估算的完成时间,但仅供参考,您不一定非要在该估算时间内完成。请尽力而为,认真解答每一个问题。提供的解决方案只是解题的方法之一,您可以放心大胆地尝试其他方法。

前提条件

所需条件

  • Kotlin 园地

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,则将相应歌曲视为不流行。
  • 一个按照以下格式输出歌曲介绍的方法:

"[歌名], performed by [音乐人], was released in [发行年份]."

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. 特别拍卖

通常,拍卖会上是由出价最高方来决定相应商品的价格。在这种特别拍卖中,如果某件商品没有人出价,则会以最低价格自动售卖给拍卖行。

在以下代码段提供的初始代码中,您会获得一个 auctionPrice() 函数,它接受可为 null 的 Bid? 类型作为实参:

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 检查,以根据各种类属性是否为 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() 方法,您需要将 Phone 类中的方法设置为可覆盖,具体方法是在该方法前面添加 open 关键字。

该解决方案包含一个具有默认构造函数的 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 基础知识课程。如需跳转到特定主题,请前往知识图谱,查看上述课程涵盖的主题列表。