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
的显式类型声明,但为了清晰起见,通常最好包含它。
随着条件语句的复杂性增加,您可能会考虑使用when 表达式替换 if-else 表达式,如下例所示
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 值的引用,如下例所示
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
值的函数。
您可以通过传递一个 String
和一个满足另一个输入参数的函数来调用 stringMapper()
,即一个接受 String
作为输入并输出 Int
的函数,如下例所示
stringMapper("Android", { input ->
input.length
})
如果匿名函数是函数定义的最后一个参数,则可以将其传递到用于调用函数的括号之外,如下例所示
stringMapper("Android") { input ->
input.length
}
在整个 Kotlin 标准库中都可以找到匿名函数。有关更多信息,请参阅 高阶函数和 Lambda 表达式。
类
到目前为止提到的所有类型都内置于 Kotlin 编程语言中。如果您想添加自己的自定义类型,可以使用 class
关键字定义一个类,如下例所示
class Car
属性
类使用属性表示状态。属性是类级别的变量,可以包含 getter、setter 和后端字段。由于汽车需要车轮才能行驶,因此您可以将 Wheel
对象列表作为 Car
的属性添加,如下例所示
class Car {
val wheels = listOf<Wheel>()
}
请注意,wheels
是一个 public val
,这意味着可以从 Car
类外部访问 wheels
,并且不能重新分配。如果要获取 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 模式的指南。