Kotlin 是 Android 开发者广泛使用的编程语言。本主题将作为 Kotlin 速成课程,助您快速入门。
变量声明
Kotlin 使用两个不同的关键字来声明变量:val
和 var
。
- 使用
val
来声明值永不改变的变量。您不能对使用val
声明的变量重新赋值。 - 使用
var
来声明值可变的变量。
在以下示例中,count
是一个类型为 Int
的变量,其初始值为 10
var count: Int = 10
Int
是一种表示整数的类型,也是 Kotlin 中可以表示的众多数值类型之一。与其他语言类似,您还可以根据数值数据使用 Byte
、Short
、Long
、Float
和 Double
。
var
关键字表示您可以根据需要重新为 count
赋值。例如,您可以将 count
的值从 10
更改为 15
var count: Int = 10
count = 15
不过,有些值不应被更改。请考虑一个名为 languageName
的 String
。如果您想确保 languageName
始终持有“Kotlin”值,则可以使用 val
关键字声明 languageName
val languageName: String = "Kotlin"
这些关键字可让您明确指定哪些内容可以更改。请根据需要加以利用。如果变量引用必须可重新赋值,请将其声明为 var
。否则,请使用 val
。
类型推断
在前面的示例中,当您为 languageName
赋值时,Kotlin 编译器可以根据所赋值的类型推断其类型。
由于 "Kotlin"
的值类型为 String
,编译器会推断出 languageName
也是一个 String
。请注意,Kotlin 是一种 静态类型 语言。这意味着该类型在编译时解析,并且永不更改。
在以下示例中,languageName
被推断为 String
,因此您不能调用任何不属于 String
类的函数
val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()
// Fails to compile
languageName.inc()
toUpperCase()
是一个只能在类型为 String
的变量上调用的函数。由于 Kotlin 编译器已将 languageName
推断为 String
,因此您可以安全地调用 toUpperCase()
。然而,inc()
是一个 Int
运算符函数,因此无法在 String
上调用。Kotlin 的类型推断方法兼具简洁性和类型安全性。
空安全
在某些语言中,可以在不提供初始显式值的情况下声明引用类型变量。在这些情况下,变量通常包含 null 值。Kotlin 变量默认不能包含 null 值。这意味着以下代码段无效
// Fails to compile
val languageName: String = null
变量若要包含 null 值,则必须是 可空 类型。您可以通过在其类型后添加 ?
来指定变量可空,如以下示例所示
val languageName: String? = null
对于 String?
类型,您可以将 String
值或 null
赋值给 languageName
。
您必须谨慎处理可空变量,否则可能遇到可怕的 NullPointerException
。例如,在 Java 中,如果您尝试对 null 值调用方法,您的程序就会崩溃。
Kotlin 提供了多种机制来安全地处理可空变量。如需了解详情,请参阅Android 中的常用 Kotlin 模式:可空性。
条件语句
Kotlin 具有多种实现条件逻辑的机制。其中最常见的是 if-else 语句。如果 if
关键字旁边的括号中的表达式求值为 true
,则会执行该分支中的代码(即紧随其后且用大括号括起来的代码)。否则,将执行 else
分支中的代码。
if (count == 42) {
println("I have the answer.")
} else {
println("The answer eludes me.")
}
您可以使用 else if
表示多个条件。这可让您在单个条件语句中表示更精细、更复杂的逻辑,如以下示例所示
if (count == 42) {
println("I have the answer.")
} else if (count > 35) {
println("The answer is close.")
} else {
println("The answer eludes me.")
}
条件语句可用于表示有状态逻辑,但您可能会发现在编写时会重复自己。在上面的示例中,您只需在每个分支中打印一个 String
。为了避免这种重复,Kotlin 提供了 条件表达式。最后一个示例可以重写如下
val answerString: String = if (count == 42) {
"I have the answer."
} else if (count > 35) {
"The answer is close."
} else {
"The answer eludes me."
}
println(answerString)
隐式地,每个条件分支都会返回其最后一行的表达式结果,因此您无需使用 return
关键字。由于所有三个分支的结果都是 String
类型,因此 if-else 表达式的结果也是 String
类型。在此示例中,answerString
从 if-else 表达式的结果中获取初始值。类型推断可用于省略 answerString
的显式类型声明,但为了清晰起见,通常最好将其包含在内。
随着条件语句复杂度的增加,您可以考虑将 if-else 表达式替换为 when 表达式,如以下示例所示
val answerString = when {
count == 42 -> "I have the answer."
count > 35 -> "The answer is close."
else -> "The answer eludes me."
}
println(answerString)
在 when
表达式中,每个分支都由一个条件、一个箭头 (->
) 和一个结果表示。如果箭头的左侧条件求值为 true,则返回右侧表达式的结果。请注意,执行不会从一个分支转到下一个分支。when
表达式示例中的代码在功能上与上一个示例中的代码等效,但可读性更高。
Kotlin 的条件语句突出显示了其更强大的功能之一,即 智能类型转换。与其使用安全调用运算符或非 null 断言运算符来处理可空值,您不如使用条件语句检查变量是否包含 null 值的引用,如以下示例所示
val languageName: String? = null
if (languageName != null) {
// No need to write languageName?.toUpperCase()
println(languageName.toUpperCase())
}
在条件分支中,languageName
可以被视为非空。Kotlin 足够智能,可以识别执行该分支的条件是 languageName
不包含 null 值,因此您无需在该分支中将 languageName
视为可空。这种智能类型转换适用于 null 检查、类型检查或满足契约的任何条件。
函数
您可以将一个或多个表达式组合成一个 函数。您无需每次需要结果时都重复相同的系列表达式,而是可以将表达式封装在一个函数中并调用该函数。
要声明函数,请使用 fun
关键字,后跟函数名称。接下来,定义函数接受的输入类型(如果有)并声明它返回的输出类型。函数体是您定义表达式的位置,这些表达式在调用函数时被调用。
基于前面的示例,这是一个完整的 Kotlin 函数
fun generateAnswerString(): String {
val answerString = if (count == 42) {
"I have the answer."
} else {
"The answer eludes me"
}
return answerString
}
上面示例中的函数名为 generateAnswerString
。它不接受任何输入。它输出类型为 String
的结果。要调用函数,请使用其名称,后跟调用运算符 (()
)。在以下示例中,answerString
变量使用 generateAnswerString()
的结果进行初始化。
val answerString = generateAnswerString()
函数可以接受参数作为输入,如以下示例所示
fun generateAnswerString(countThreshold: Int): String {
val answerString = if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
return answerString
}
声明函数时,您可以指定任意数量的参数及其类型。在上面的示例中,generateAnswerString()
接受一个名为 countThreshold
、类型为 Int
的参数。在函数内部,您可以使用其名称来引用该参数。
调用此函数时,您必须在函数调用的括号内包含一个参数
val answerString = generateAnswerString(42)
简化函数声明
generateAnswerString()
是一个相当简单的函数。该函数声明一个变量然后立即返回。当函数返回单个表达式的结果时,您可以通过直接返回函数中包含的 if-else 表达式的结果来跳过声明局部变量,如以下示例所示
fun generateAnswerString(countThreshold: Int): String {
return if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
}
您还可以使用赋值运算符替换 return 关键字
fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
"I have the answer"
} else {
"The answer eludes me"
}
匿名函数
并非所有函数都需要名称。有些函数更直接地通过其输入和输出来标识。这些函数称为 匿名函数。您可以保留对匿名函数的引用,稍后使用此引用调用匿名函数。您还可以像其他引用类型一样,在应用中传递该引用。
val stringLengthFunc: (String) -> Int = { input ->
input.length
}
与命名函数一样,匿名函数可以包含任意数量的表达式。函数的返回值是最终表达式的结果。
在上面的示例中,stringLengthFunc
包含对匿名函数的引用,该函数接受 String
作为输入,并返回输入 String
的长度作为类型为 Int
的输出。因此,函数的类型表示为 (String) -> Int
。但是,此代码不会调用函数。要检索函数的结果,您必须像调用命名函数一样调用它。调用 stringLengthFunc
时,您必须提供一个 String
,如以下示例所示
val stringLengthFunc: (String) -> Int = { input ->
input.length
}
val stringLength: Int = stringLengthFunc("Android")
高阶函数
函数可以接受另一个函数作为参数。将其他函数用作参数的函数称为 高阶函数。此模式对于组件之间的通信非常有用,就像您在 Java 中使用回调接口一样。
这是一个高阶函数的示例
fun stringMapper(str: String, mapper: (String) -> Int): Int {
// Invoke function
return mapper(str)
}
stringMapper()
函数接受一个 String
以及一个从您传递给它的 String
中派生 Int
值的函数。
您可以调用 stringMapper()
,方法是传入一个 String
以及一个满足其他输入参数的函数,即一个接受 String
作为输入并输出 Int
的函数,如以下示例所示
stringMapper("Android", { input ->
input.length
})
如果匿名函数是函数上定义的 最后一个 参数,您可以将其传递到用于调用函数的括号外部,如以下示例所示
stringMapper("Android") { input ->
input.length
}
匿名函数在 Kotlin 标准库中随处可见。如需了解详情,请参阅高阶函数和 Lambda 表达式。
类
到目前为止提及的所有类型都内置于 Kotlin 编程语言中。如果您想添加自己的自定义类型,可以使用 class
关键字定义一个类,如以下示例所示
class Car
属性
类使用属性来表示状态。一个属性是类级别的变量,可以包含 getter、setter 和 backing field。由于汽车需要车轮才能行驶,您可以将 Wheel
对象列表作为 Car
的属性添加,如以下示例所示
class Car {
val wheels = listOf<Wheel>()
}
请注意,wheels
是一个 public val
,这意味着 wheels
可以从 Car
类外部访问,并且不能重新赋值。如果您想获取 Car
的实例,则必须首先调用其构造函数。从那里,您可以访问其任何可访问的属性。
val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car
如果您想自定义车轮,可以定义一个自定义构造函数来指定类属性的初始化方式
class Car(val wheels: List<Wheel>)
在上面的示例中,类构造函数接受一个 List<Wheel>
作为构造函数参数,并使用该参数初始化其 wheels
属性。
类函数和封装
类使用函数来建模行为。函数可以修改状态,帮助您仅公开您希望公开的数据。这种访问控制是更大范围的面向对象概念的一部分,称为 封装。
在以下示例中,doorLock
属性对 Car
类外部的任何内容都保持私有。要解锁汽车,您必须调用 unlockDoor()
函数并传入有效的密钥,如以下示例所示
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
如果您想自定义属性的引用方式,可以提供自定义 getter 和 setter。例如,如果您想公开属性的 getter,同时限制对其 setter 的访问,则可以将该 setter 指定为 private
class Car(val wheels: List<Wheel>) {
private val doorLock: DoorLock = ...
var gallonsOfFuelInTank: Int = 15
private set
fun unlockDoor(key: Key): Boolean {
// Return true if key is valid for door lock, false otherwise
}
}
结合属性和函数,您可以创建可对所有类型对象建模的类。
互操作性
Kotlin 最重要的特性之一是其与 Java 的流畅互操作性。由于 Kotlin 代码会编译为 JVM 字节码,您的 Kotlin 代码可以直接调用 Java 代码,反之亦然。这意味着您可以直接从 Kotlin 中利用现有的 Java 库。此外,大多数 Android API 都是用 Java 编写的,您可以直接从 Kotlin 中调用它们。
后续步骤
Kotlin 是一种灵活、实用的语言,拥有不断增长的支持和发展势头。如果您尚未尝试,我们鼓励您试一试。后续步骤,请查看官方 Kotlin 文档以及有关如何在 Android 应用中应用常用 Kotlin 模式的指南。