泛型、对象和扩展

1. 简介

数十年来,编程人员设计了多种编程语言功能来帮助您编写更好的代码,例如使用更少的代码表达相同的想法、通过抽象来表达复杂的想法,以及编写代码来防止其他开发者不小心出错。Kotlin 语言也不例外,其中许多功能都旨在帮助开发者编写更具表现力的代码。

遗憾的是,如果您是第一次编程,这些功能可能会让工作复杂化。虽然听起来很有用,但这些功能的实用程度及其解决的问题有时可能并不明显。您可能已经在 Compose 和其他库中见到过对某些功能的运用了。

虽然经验是无可替代的,但此 Codelab 将带您了解 Kotlin 的一些概念,以帮助您构建更大的应用:

  • 泛型
  • 不同类型的类(枚举类和数据类)
  • 单例对象和伴生对象
  • 扩展属性和函数
  • 作用域函数

此 Codelab 结束时,您应该会更深入地了解在本课程中学过的代码,并学习一些关于何时会在您自己的应用中遇到或使用这些概念的示例。

前提条件

  • 熟悉面向对象的编程概念,包括继承。
  • 如何定义和实现接口。

学习内容

  • 如何为类定义通用类型形参。
  • 如何实例化通用类。
  • 何时使用枚举类和数据类。
  • 如何定义必须实现接口的通用类型形参。
  • 如何使用作用域函数来访问类属性和方法。
  • 如何为类定义单例对象和伴生对象。
  • 如何使用新的属性和方法来扩展现有的类。

所需条件

  • 一个能够访问 Kotlin 园地的网络浏览器。

2. 使用泛型创建可重复使用的类

假设您正在为某项在线测验编写一款应用,此测验类似于您在本课程中见到过的测验。测验问题通常有多种类型,例如填空或判断正误。单个测验问题可由具有多个属性的类表示。

测验中的问题文本可由字符串表示。测验问题还需要将答案表现出来。不过,不同类型的问题(例如判断正误)可能需要使用不同的数据类型来表现答案。下面我们来定义三种不同类型的问题。

  • 填空题:答案是由 String 表示的字词。
  • 判断正误:答案由 Boolean 表示。
  • 数学题:答案是数值。简单算术题的答案由 Int 表示。

此外,示例中的测试问题还包含难度等级,无论什么类型的问题都不例外。难度等级由字符串表示,且具有三个可能的值:"easy""medium""hard"

定义用于表示各类测验问题的类:

  1. 前往 Kotlin 园地
  2. main() 函数上方,为填空题定义一个名为 FillInTheBlankQuestion 的类,其中包含适用于 questionTextString 属性、适用于 answerString 属性以及适用于 difficultyString 属性。
class FillInTheBlankQuestion(
    val questionText: String,
    val answer: String,
    val difficulty: String
)
  1. FillInTheBlankQuestion 类下方,为判断正误题定义另一个名为 TrueOrFalseQuestion 的类,其中包含适用于 questionTextString 属性、适用于 answerBoolean 属性以及适用于 difficultyString 属性。
class TrueOrFalseQuestion(
    val questionText: String,
    val answer: Boolean,
    val difficulty: String
)
  1. 最后,在其他两个类下方,定义一个 NumericQuestion 类,其中包含适用于 questionTextString 属性、适用于 answerInt 属性以及适用于 difficultyString 属性。
class NumericQuestion(
    val questionText: String,
    val answer: Int,
    val difficulty: String
)
  1. 看一下您编写的代码。您发现重复代码了吗?
class FillInTheBlankQuestion(
    val questionText: String,
    val answer: String,
    val difficulty: String
)

class TrueOrFalseQuestion(
    val questionText: String,
    val answer: Boolean,
    val difficulty: String
)
class NumericQuestion(
    val questionText: String,
    val answer: Int,
    val difficulty: String
)

这三个类具有以下完全相同的属性:questionTextanswerdifficulty。唯一的区别在于 answer 属性的数据类型。您可能会认为,显而易见的解决方案是使用 questionTextdifficulty 创建一个父类,并让每个子类都定义 answer 属性。

不过,使用继承也存在上述问题。每次添加新类型的问题时,您都必须添加 answer 属性。唯一的区别在于数据类型。父类 Question 没有答案属性也显得很奇怪。

如果您想让某个属性具有不同的数据类型,创建子类并不是理想的解决方案。相反,Kotlin 提供了一种称为“通用类型”的元素,可让您的单个属性根据特定用例具有不同的数据类型。

什么是通用数据类型?

通用类型(简称“泛型”)允许数据类型(例如类)指定可与其属性和方法结合使用的未知占位符数据类型。这具体意味着什么?

在上述示例中,您无需为每个可能的数据类型分别定义答案属性,只需创建单个类来表示所有问题,并为 answer 的数据类型使用一个占位符名称即可。系统会在实例化相应的类时指定实际数据类型(StringIntBoolean 等)。无论在何处使用占位符名称,系统都会改为使用传入类的数据类型。为类定义通用类型的语法如下所示:

10a38dbaa8f10ec6.png

系统会在实例化类时提供通用数据类型,因此需要将其定义为类签名的一部分。类名称后面是左尖括号 (<),后跟表示数据类型的占位符名称,再后面是右尖括号 (>)。

然后,此占位符名称即可在您使用类中的实际数据类型的任何位置使用了(例如用于某个属性)。

ec3dcacd1a216bd4.png

除了使用占位符名称(而非数据类型)这一点以外,这与所有其他属性声明并无区别。

类最终是如何确定要使用哪种数据类型的呢?系统会在您实例化类时,将通用类型使用的数据类型作为用尖括号括住的形参进行传递。

4a21173cb6d2451b.png

类名称后面是左尖括号 (<),后跟实际数据类型(StringBooleanInt 等),再后面是右尖括号 (>)。您为通用属性传入的值的数据类型必须与尖括号中的数据类型一致。您将对答案属性进行通用化处理,以便可以使用一个类来表示任何类型的测验问题,无论答案是 StringBooleanInt 还是任意数据类型。

重构代码以使用泛型

重构您的代码,以使用一个名为 Question 且具有通用答案属性的类。

  1. 移除 FillInTheBlankQuestionTrueOrFalseQuestionNumericQuestion 的类定义。
  2. 创建一个名为 Question 的新类。
class Question()
  1. 在类名称之后、括号之前的位置,使用左右尖括号添加一个通用类型形参。调用通用类型 T
class Question<T>()
  1. 添加 questionTextanswerdifficulty 属性。questionText 的类型应为 Stringanswer 的类型应为 T,因为其数据类型是在实例化 Question 类时指定的。difficulty 属性的类型为 String
class Question<T>(
    val questionText: String,
    val answer: T,
    val difficulty: String
)
  1. 如需了解其在有多个问题类型(填空、判断正误等)时的运作方式,请在 main() 中创建三个 Question 类的实例,如下所示。
fun main() {
    val question1 = Question<String>("Quoth the raven ___", "nevermore", "medium")
    val question2 = Question<Boolean>("The sky is green. True or false", false, "easy")
    val question3 = Question<Int>("How many days are there between full moons?", 28, "hard")
}
  1. 运行您的代码以确保一切正常。现在,您应该有三个 Question 类实例,且每个实例都具有不同的答案数据类型,而不是三个不同的类或使用继承。如果您想处理其他答案类型的问题,可以重复使用同一 Question 类。

3. 使用枚举类

在上一部分中,您定义了一个难度属性,它具有以下三个可能的值:“easy”、“medium”和“hard”。虽然此方法有效,但也存在几个问题。

  1. 如果您不小心输错了三个可能的字符串中的一个,就可能会引入 bug。
  2. 如果值发生更改(例如,"medium" 重命名为 "average"),则需要更新字符串的所有用法。
  3. 没有任何机制能够阻止您或其他开发者不小心使用这三个有效值以外的其他字符串。
  4. 如果您添加更多难度级别,代码会更难维护。

Kotlin 使用一种名为“枚举类”的特殊类来帮助您解决这些问题。枚举类用于创建具有一组数量有限的可能值的类型。例如,在现实世界中,您可以使用一个枚举类来表示四个基本方向(北、南、东和西)。您不需要(代码也不应允许)使用任何其他方向。枚举类的语法如下所示。

2046d73e89bd8167.png

枚举的每个可能值都称为“枚举常量”。枚举常量位于构造函数内,互相以英文逗号分隔。按照惯例,常量名称中的每个字母都要大写。

您可以使用点运算符来引用枚举常量。

d6f72b0c1f3218df.png

使用枚举常量

修改代码,以便使用枚举常量(而不是 String)来表示难度。

  1. Question 类下方,定义一个名为 Difficultyenum 类。
enum class Difficulty {
    EASY, MEDIUM, HARD
}
  1. Question 类中,将 difficulty 属性的数据类型从 String 更改为 Difficulty
class Question<T>(
    val questionText: String,
    val answer: T,
    val difficulty: Difficulty
)
  1. 初始化这三个问题时,传入枚举常量作为难度。
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)

4. 使用数据类

到目前为止,您使用过的许多类(例如 Activity 的子类)都包含多种用于执行不同操作的方法。这些类不仅表示数据,还包含许多功能。

另一方面,像 Question 这样的类只包含数据。它们没有任何用于执行操作的方法。这些类可以定义为“数据类”。通过将类定义为数据类,Kotlin 编译器可以做出某些假设,并自动实现某些方法。例如,println() 函数会在后台调用 toString()。当您使用数据类时,系统会根据类的属性自动实现 toString() 和其他方法。

如需定义数据类,只需在 class 关键字前面添加 data 关键字即可。

4f6effa88d56a850.png

Question 转换为数据类

首先,您会看到,当您对非数据类调用 toString() 等方法时,会发生什么情况。然后,您要将 Question 转换为数据类,以便让系统默认实现此方法和其他方法。

  1. main() 中,输出对 question1 调用 toString() 的结果。
fun main() {
    val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
    val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
    val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
    println(question1.toString())
}
  1. 运行您的代码。输出结果仅显示类名称和对象的唯一标识符。
Question@37f8bb67
  1. 使用 data 关键字将 Question 转换为数据类。
data class Question<T>(
    val questionText: String,
    val answer: T,
    val difficulty: Difficulty
)
  1. 再次运行您的代码。通过将其转换为数据类,Kotlin 能够确定在调用 toString() 时如何显示此类的属性。
Question(questionText=Quoth the raven ___, answer=nevermore, difficulty=MEDIUM)

将某个类定义为数据类型后,系统会实现以下方法。

  • equals()
  • hashCode():在使用某些集合类型时,您会看到此方法。
  • toString()
  • componentN()component1()component2()
  • copy()

5. 使用单例对象

在很多情况下,您都需要让一个类只包含一个实例。例如:

  1. 移动游戏中当前用户的玩家统计信息。
  2. 与单个硬件设备互动,例如通过扬声器发送音频。
  3. 用于访问远程数据源(例如 Firebase 数据库)的对象。
  4. 身份验证(每次只应有一位用户登录)。

在上述情况下,您可能需要使用类。不过,您只需要对此类的一个实例进行实例化。如果只有一台硬件设备,或者一次只有一位用户登录,则没有理由创建多个实例。让两个对象同时访问同一硬件设备可能会导致出现一些非常奇怪且有 bug 的行为。

在代码中,您可以明确地说明某个对象只应有一个实例,只需将其定义为单例即可。“单例”是指只能有一个实例的类。Kotlin 提供了一种称为“对象”的特殊结构,可用于构建单例类。

定义单例对象

81e0355283d36761.png

对象的语法与类的语法类似。只需使用 object 关键字(而不是 class 关键字)即可。单例对象不能包含构造函数,因为您无法直接创建实例。相反,所有属性都要在大括号内定义并被赋予初始值。

前面给出的一些示例可能看起来并不明显,尤其是如果您尚未使用特定的硬件设备或尚未在应用中处理身份验证的话。不过,随着您继续学习 Android 开发,单例对象就会出现在您面前。让我们通过一个简单示例来看看它是怎样实际运用的,该示例使用了一个用户状态对象,其中只需要一个实例。

对于测验,最好能够通过某种方法来跟踪问题总数,以及学生到目前为止已经回答的问题数量。您只需让此类的一个实例存在即可,因此要将其声明为单例对象,而不是类。

  1. 创建一个名为 StudentProgress 的对象。
object StudentProgress {
}
  1. 在本例中,我们假设共有 10 个问题,并且到目前为止已经回答了其中的三个问题。添加两个 Int 属性:total(值为 10)和 answered(值为 3)。
object StudentProgress {
    var total: Int = 10
    var answered: Int = 3
}

访问单例对象

还记得您无法直接创建单例对象实例吗?那么,如何才能访问其属性呢?

由于一次只能存在一个 Progress 实例,因此您可以通过以下方法来访问其属性:引用对象本身的名称,后跟点运算符 (.),再后跟属性名称。

2ed33b669a8d055c.png

更新 main() 函数以访问单例对象的属性。

  1. main() 中,添加对 println() 的调用,以便输出 StudentProgress 对象中的 answeredtotal 问题。
fun main() {
    ...
    println("${StudentProgress.answered} of ${StudentProgress.total} answered.")
}
  1. 运行您的代码,验证是否一切正常。
...
3 of 10 answered

将对象声明为伴生对象

Kotlin 中的类和对象可以在其他类型中定义,是整理代码的绝佳方式。您可以使用“伴生对象”在另一个类中定义单例对象。伴生对象允许您从类内部访问其属性和方法(如果对象的属性和方法属于相应类的话),从而让语法变得更简洁。

如需声明伴生对象,只需在 object 关键字前面添加 companion 关键字即可。

e65d858bb7b607c4.png

您将创建一个名为 Quiz 的新类来存储测验问题,并将 StudentProgress 设为 Quiz 类的伴生对象。

  1. Difficulty 枚举下方,定义一个名为 Quiz 的新类。
class Quiz {
}
  1. question1question2question3main() 移至 Quiz 类。如果尚未移除 println(question1.toString()),您还需要将其移除。
class Quiz {
    val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
    val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
    val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)

}
  1. StudentProgress 对象移入 Quiz 类中。
class Quiz {
    val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
    val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
    val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)

    object StudentProgress {
        var total: Int = 10
        var answered: Int = 3
    }
}
  1. 使用 companion 关键字标记 StudentProgress 对象。
companion object StudentProgress {
    var total: Int = 10
    var answered: Int = 3
}
  1. 更新对 println() 的调用,以便使用 Quiz.answeredQuiz.total 来引用属性。虽然这些属性是在 StudentProgress 对象中声明的,但只需使用 Quiz 类的名称即可通过点表示法来访问它们。
fun main() {
    println("${Quiz.answered} of ${Quiz.total} answered.")
}
  1. 运行代码,验证输出。
3 of 10

6. 使用新的属性和方法来扩展类

如果您使用 Compose,在指定界面元素的大小时,您可能会发现一些有趣的语法。数字类型(例如 Double)似乎具有各种属性(例如指定 dpsp 的维度)。

eb15d8b633c2b813.png

为什么 Kotlin 语言的设计人员会在内置数据类型上添加属性和函数,特别是用于构建 Android 界面的属性和函数?难道他们能预测未来吗?难道 Kotlin 早在 Compose 存在之前就能与 Compose 结合使用?

显然不能这样!在编写类时,您通常并不知道其他开发者究竟会(或计划)怎样在其应用中使用该类。您无法预测未来的所有用例,而因为一些不可预见的用例让代码出现不必要的膨胀也并非明智之举。

Kotlin 的解决方案就是让其他开发者能够扩展现有数据类型,添加可通过点语法访问的属性和方法,就像这些元素本就包含在相应数据类型中一样。没有在 Kotlin 中处理过浮点类型的开发者(例如构建 Compose 库的开发者)可能会选择添加特定于界面维度的属性和方法。

由于您在前两个单元中学习 Compose 时已经见过此语法,因此接下来您将了解其后台运作方式。您需要添加一些属性和方法以扩展现有类型。

添加扩展属性

如需定义扩展属性,请在变量名称前面添加类型名称和点运算符 (.)。

e4ad1e0f91ac8583.png

您将重构 main 中的代码,以便将测验进度输出到扩展属性中。

  1. Quiz 类下方,定义一个名为 progressText 且类型为 StringQuiz.StudentProgress 扩展属性。
val Quiz.StudentProgress.progressText: String
  1. 为此扩展属性定义一个 getter,用于返回之前在 main() 中使用的那个字符串。
val Quiz.StudentProgress.progressText: String
    get() = "${answered} of ${total} answered"
  1. main() 中的代码替换为输出 progressText 的代码。由于这是伴生对象的扩展属性,因此您可以使用类名称 Quiz 通过点表示法来访问此属性。
fun main() {
    println(Quiz.progressText)
}
  1. 运行您的代码以验证其能否正常运行。
3 of 10 answered

添加扩展函数

如需定义扩展函数,请在函数名称前面添加类型名称和点运算符 (.)。

495b8e34d337ec73.png

您将添加一个扩展函数,以便将测验进度输出为进度条。由于您无法真的在 Kotlin 园地中构建进度条,因此您将使用文本来输出一个复古风的进度条!

  1. StudentProgress 对象添加一个名为 printProgressBar() 的扩展函数。此函数不应接受任何形参,也没有任何返回值。
fun Quiz.StudentProgress.printProgressBar() {
}
  1. 使用 repeat() 输出 字符,次数为 answered。进度条的这块深色阴影部分表示已经回答的问题数量。由于无需在每输出一个字符后都进行换行,因此请使用 print()
fun Quiz.StudentProgress.printProgressBar() {
    repeat(Quiz.answered) { print("▓") }
}
  1. 使用 repeat() 输出 字符,次数等于 totalanswered 的差值。进度条中的这块浅色阴影部分表示剩余问题。
fun Quiz.StudentProgress.printProgressBar() {
    repeat(Quiz.answered) { print("▓") }
    repeat(Quiz.total - Quiz.answered) { print("▒") }
}
  1. 使用不含任何实参的 println() 输出一个新的行,然后输出 progressText
fun Quiz.StudentProgress.printProgressBar() {
    repeat(Quiz.answered) { print("▓") }
    repeat(Quiz.total - Quiz.answered) { print("▒") }
    println()
    println(Quiz.progressText)
}
  1. 更新 main() 中的代码以调用 printProgressBar()
fun main() {
    Quiz.printProgressBar()
}
  1. 运行代码,验证输出。
▓▓▓▒▒▒▒▒▒▒
3 of 10 answered

有哪项操作是强制性要求吗?当然不是。不过,如果设置了扩展属性和方法选项,您在向其他开发者展示代码时就会有更多的选择。对其他类型的代码使用点语法可以使代码更易于阅读,无论是对您自己还是对其他开发者来说都是如此。

7. 使用作用域函数来访问类属性和方法

如您所见,Kotlin 包含大量功能,可让您的代码变得更简洁。

随着您继续学习 Android 开发,您会遇到这样一个功能,那就是“作用域函数”。借助作用域函数,您可以通过简洁的方式访问类中的属性和方法,而无需重复访问变量名称。这具体意味着什么?下面我们来看一个示例。

使用作用域函数消除重复的对象引用

作用域函数属于高阶函数,允许您在不引用对象名称的情况下访问对象的属性和方法。之所以将这些函数称为作用域函数,是因为传入的函数的正文会采用用于调用作用域函数的对象的作用域。例如,有些作用域函数允许您访问类中的属性和方法,就好像这些函数已被定义为相应类的方法一样。这样一来,如果包含对象名称会使代码变得冗余,您便可以省略对象名称,从而让代码更具可读性。

为了更好地说明这一点,让我们来看一下您将在本课程的后续内容中遇到的几个不同的作用域函数。

使用 let() 替换过长的对象名称

借助 let() 函数,您可以使用标识符 it 来引用 lambda 表达式中的对象,而无需使用对象的实际名称。这有助于避免在访问多个属性时反复使用过长、更具描述性的对象名称。let() 函数是一个扩展函数,可通过点表示法对任何 Kotlin 对象进行调用。

尝试使用 let() 访问 question1question2question3 的属性:

  1. Quiz 类添加一个名为 printQuiz() 的函数。
fun printQuiz() {

}
  1. 添加以下代码,以便输出问题的 questionTextanswerdifficulty。虽然系统会访问 question1question2question3 的多个属性,但每次都会使用整个变量名称。如果变量的名称发生更改,您就需要对每个用法进行相应更改。
fun printQuiz() {
    println(question1.questionText)
    println(question1.answer)
    println(question1.difficulty)
    println()
    println(question2.questionText)
    println(question2.answer)
    println(question2.difficulty)
    println()
    println(question3.questionText)
    println(question3.answer)
    println(question3.difficulty)
    println()
}
  1. 使用 question1question2question3 上的 let() 函数调用将访问 questionTextanswerdifficulty 属性的代码括起来。用它替换每个 lambda 表达式中的变量名称。
fun printQuiz() {
    question1.let {
        println(it.questionText)
        println(it.answer)
        println(it.difficulty)
    }
    println()
    question2.let {
        println(it.questionText)
        println(it.answer)
        println(it.difficulty)
    }
    println()
    question3.let {
        println(it.questionText)
        println(it.answer)
        println(it.difficulty)
    }
    println()
}
  1. 创建 Quiz 类的实例,并将其命名为 quiz
fun main() {
    Quiz.printProgressBar()
    val quiz = Quiz()
}
  1. 调用 printQuiz()
fun main() {
    Quiz.printProgressBar()
    val quiz = Quiz()
    quiz.printQuiz()
}
  1. 运行您的代码,验证是否一切正常。
Quoth the raven ___
nevermore
MEDIUM

Quoth the raven ___
nevermore
MEDIUM

Quoth the raven ___
nevermore
MEDIUM

使用 apply() 在没有变量的情况下调用对象方法

作用域函数有一项非常棒的功能,那就是即使尚未将某个对象分配到变量,您也可以对此对象调用作用域函数。例如,apply() 函数是一个扩展函数,可通过点表示法对对象进行调用。apply() 函数还会返回对相应对象的引用,以便将其存储在变量中。

更新 main() 中的代码以调用 apply() 函数。

  1. 在创建 Quiz 类的实例时,请在右圆括号后面调用 apply()。您可以在调用 apply() 时省略圆括号,并使用尾随 lambda 语法。
val quiz = Quiz().apply {
}
  1. 将对 printQuiz() 的调用移至 lambda 表达式内。您无需再引用 quiz 变量或使用点表示法。
val quiz = Quiz().apply {
    printQuiz()
}
  1. apply() 函数会返回 Quiz 类的实例,但由于您在任何位置都不再使用此实例了,因此请移除 quiz 变量。借助 apply() 函数,您甚至无需变量即可对 Quiz 实例调用方法。
Quiz().apply {
    printQuiz()
}
  1. 运行您的代码。请注意,您可以在不引用 Quiz 实例的情况下调用此方法。apply() 函数返回了存储在 quiz 中的对象。
▓▓▓▒▒▒▒▒▒▒
3 of 10 answered.
kotlin.Unit
Quoth the raven ___
nevermore
MEDIUM

Quoth the raven ___
nevermore
MEDIUM

Quoth the raven ___
nevermore
MEDIUM

虽然并不强制要求您使用作用域函数来实现所需输出,但上述示例说明了作用域函数如何能够让您的代码变得更简洁,并避免重复使用相同的变量名称。

以上代码仅展示了两个示例,但我们建议您为作用域函数文档添加标签并参阅此文档,因为您会在本课程的后续内容中遇到使用此类函数的内容。

8. 摘要

您刚刚了解了 Kotlin 的一些新功能的实际运用。泛型支持将数据类型作为形参传递到类,枚举类可以定义有限数量的可能值,而数据类有助于为类自动生成一些有用的方法。

您还了解了如何创建单例对象(仅限一个实例)、如何使其成为另一个类的伴生对象,以及如何使用新的 get-only 属性和新的方法来扩展现有的类。最后,您看到了一些示例,了解了作用域函数如何能够在访问属性和方法时提供更简洁的语法。

随着您深入学习 Kotlin、Android 开发和 Compose,您将在后续单元中见到这些概念。现在,您对它们的运作方式以及它们如何能够提升代码的可重用性和可读性有了更深入的了解。

9. 了解详情