学习 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 编译器已将 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 模式的指南。