เกี่ยวกับ Codelab นี้
1 准备工作
您已经下大力气来学习 Kotlin 编程的基础知识,现在是时候将所学知识付诸实践了。
这里的练习可以测试您对所学概念的理解程度,都是基于实际用例,其中有些用例您可能之前就遇到过。
请按照相关说明在 Kotlin 园地中寻找各项练习的解决方案。如果您遇到困难,某些练习还会给出提示来帮助您解题。各项练习的解决方案代码就在文末,不过建议您先解题,然后再看答案。
您可以按照自己的节奏来完成练习。这些练习都有估算的完成时间,但仅供参考,您不一定非要在该估算时间内完成。请尽力而为,认真解答每一个问题。提供的解决方案只是解题的方法之一,您可以放心大胆地尝试其他方法。
前提条件
- 熟悉 Kotlin 园地
- 能够定义和调用函数。
- 了解 Kotlin 编程基础知识,包括变量以及
println()
和main()
函数。 - 熟悉 Kotlin 条件语句,包括
if/else
和when
语句和表达式 - 熟悉 Kotlin lambda 表达式
- 了解如何处理可为 null 的变量。
- 了解如何创建 Kotlin 类和对象。
- 学完了以下 Codelab:在 Kotlin 中编写条件语句、在 Kotlin 中使用可为 null 性、在 Kotlin 中使用类和对象以及在 Kotlin 中使用函数类型和 lambda 表达式。
所需条件
- 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
属性更改为 true
或 false
值的方法,并且还会替换继承自 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 基础知识课程。如需跳转到特定主题,请前往知识图谱,查看上述课程涵盖的主题列表。