学习 Kotlin 编程语言

Kotlin 是一种编程语言,被世界各地的 Android 开发者广泛使用。本主题作为 Kotlin 速成课程,可帮助您快速入门。

变量声明

Kotlin 使用两个不同的关键字来声明变量:valvar

  • 对于值永不改变的变量,请使用 val。您不能重新分配使用 val 声明的变量的值。
  • 对于值可以改变的变量,请使用 var

在下面的示例中,count 是类型为 Int 的变量,其初始值为 10

var count: Int = 10

Int 是一种表示整数的类型,它是 Kotlin 中可以表示的众多数值类型之一。与其他语言类似,您还可以根据您的数值数据使用 ByteShortLongFloatDouble

var 关键字表示您可以根据需要重新分配 count 的值。例如,您可以将 count 的值从 10 更改为 15

var count: Int = 10
count = 15

不过,某些值不应更改。考虑一个名为 languageNameString。如果您想确保 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 编译器推断 languageNameString 类型,因此您可以安全地调用 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 模式的指南。