学习 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 的类型推断方法让您既能保持简洁,又能保证类型安全。

空安全

在某些语言中,引用类型变量可以在不提供初始显式值的情况下声明。在这种情况下,变量通常包含一个空值。默认情况下,Kotlin 变量不能保存空值。这意味着以下代码片段无效

// Fails to compile
val languageName: String = null

要使变量保存空值,它必须是 *可空* 类型。您可以通过在类型后面添加 ? 来指定变量为可空,如以下示例所示

val languageName: String? = null

使用 String? 类型,您可以将 String 值或 null 分配给 languageName

您必须小心处理可空变量,否则可能会遇到令人讨厌的 NullPointerException。例如,在 Java 中,如果您尝试在空值上调用方法,则程序会崩溃。

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表达式中的每个分支都由一个条件、一个箭头(->)和一个结果表示。如果箭头左侧的条件计算结果为真,则返回箭头右侧表达式结果。请注意,执行不会从一个分支贯穿到下一个分支。when表达式示例中的代码在功能上等同于前面示例中的代码,但可以说更容易阅读。

Kotlin 的条件语句突出了其更强大的功能之一,智能转换。与其使用安全调用运算符或非空断言运算符来处理可空值,不如使用条件语句检查变量是否包含对空值的引用,如以下示例所示

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

在条件分支内,languageName可以被视为非空。Kotlin 足够智能,可以识别出执行分支的条件是languageName不包含空值,因此您不必在该分支内将languageName视为可空。这种智能转换适用于空值检查、类型检查,或者任何满足契约的条件。

函数

您可以将一个或多个表达式组合成一个函数。与其每次都需要结果时都重复相同的表达式序列,不如将这些表达式包装在一个函数中,然后调用该函数。

要声明一个函数,请使用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,这意味着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 模式的指南。