4、Kotlin语言基础(下):函数与Lambda表达式、面向对象编程、空安全机制、集合框架

好,咱们接着聊。上一章我们把 Kotlin 的基础语法和变量类型过了一遍,这一章要进入真正有意思的部分了。说白了,这些才是你写 Android 应用时每天都要打交道的东西。我个人觉得,Kotlin 最吸引人的地方,就是它把「简洁」和「安全」做到了极致。咱们一个一个来看。

4.1 函数:不只是方法

在 Java 里,我们管它叫方法。在 Kotlin 里,它叫函数。但 Kotlin 的函数,比 Java 的方法要灵活得多。

4.1.1 函数的声明与默认参数

先看一个最简单的函数:

fun greet(name: String): String {
    return "Hello, $name"
}

嗯,这看起来和 Java 差不多。但 Kotlin 允许你给参数设置默认值。我在项目中经常用这个特性来减少重载方法的数量。

fun greet(name: String, greeting: String = "Hello"): String {
    return "$greeting, $name"
}

// 调用时可以只传一个参数
println(greet("小明"))  // 输出:Hello, 小明
println(greet("小明", "你好"))  // 输出:你好, 小明

你想想看,在 Java 里你要实现同样的效果,得写两个重载方法。Kotlin 一行就搞定了。

4.1.2 单表达式函数

如果函数体只有一行代码,你可以省略花括号,直接用等号赋值:

fun double(x: Int): Int = x * 2

// 甚至可以省略返回类型,让编译器自动推断
fun double(x: Int) = x * 2

我个人习惯在简单的工具函数里用这种写法,代码看起来非常清爽。

4.2 Lambda 表达式:函数的另一种形态

Lambda 说白了就是一段可以传递的代码块。在 Kotlin 里,Lambda 表达式是函数式编程的核心。

4.2.1 基本语法

val sum: (Int, Int) -> Int = { x, y -> x + y }

// 调用
println(sum(3, 5))  // 输出:8

箭头左边是参数,右边是函数体。如果参数只有一个,可以用 it 来隐式引用它:

val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled)  // 输出:[2, 4, 6, 8, 10]

我在做 RecyclerView 的列表过滤时,经常用这种写法,一行代码就能搞定复杂的逻辑。

4.2.2 高阶函数

Kotlin 允许函数接收另一个函数作为参数,或者返回一个函数。这种函数就叫高阶函数。

fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

// 使用
val result = operateOnNumbers(10, 5) { x, y -> x - y }
println(result)  // 输出:5

你可能会问,这有什么用?嗯,Android 里的点击事件监听,本质上就是高阶函数的应用。Kotlin 让这种模式写起来更自然。

小技巧: 如果 Lambda 是函数的最后一个参数,可以把它移到括号外面。如果 Lambda 是唯一参数,连括号都可以省略。这就是 Kotlin 的「尾随 Lambda」语法。

4.3 面向对象编程:类、继承、接口

Kotlin 的面向对象和 Java 很像,但有一些关键的区别。我刚开始从 Java 转 Kotlin 时,最不适应的就是这些差异。

4.3.1 类的声明

class Person(val name: String, var age: Int)

就这么一行?对,就这么一行。Kotlin 把构造器、属性声明、getter/setter 全部合并了。这在 Java 里至少要写几十行代码。

如果你需要更复杂的构造逻辑,可以用 init 块:

class Person(val name: String, var age: Int) {
    init {
        require(age >= 0) { "年龄不能为负数" }
        println("创建了一个人:$name,$age 岁")
    }
}

4.3.2 继承:默认是封闭的

Kotlin 的类默认是 final 的,不能被继承。如果你想允许继承,必须显式加上 open 关键字。

open class Animal(val name: String) {
    open fun makeSound() {
        println("动物发出声音")
    }
}

class Dog(name: String) : Animal(name) {
    override fun makeSound() {
        println("汪汪!")
    }
}

为什么要这样设计?Java 里经常出现「继承滥用」的问题。Kotlin 的设计者认为,继承应该是一种有意识的选择,而不是默认行为。我个人非常赞同这个设计哲学。

注意: 我曾经在项目中遇到过一个坑——忘了给基类加 open,结果子类编译报错。排查了半天才发现是这个问题。所以,如果你打算让一个类被继承,记得加上 open

4.3.3 接口:可以有默认实现

Kotlin 的接口和 Java 8 的接口类似,允许有默认方法实现:

interface Flyable {
    fun fly() {
        println("飞行中...")
    }
    
    fun land()  // 抽象方法,必须由实现类提供
}

class Bird : Flyable {
    override fun land() {
        println("小鸟着陆")
    }
}

这在做 Android 的接口回调时特别有用。你可以给接口方法提供默认实现,实现类只需要覆盖自己关心的方法即可。

4.4 空安全机制:Kotlin 最亮眼的设计

空指针异常(NullPointerException)是 Java 开发者最头疼的问题之一。Kotlin 从语言层面解决了这个问题。

4.4.1 可空类型与非空类型

在 Kotlin 里,类型默认是不可为空的:

var name: String = "Kotlin"
name = null  // 编译错误!

如果你想让一个变量可以为空,需要在类型后面加 ?

var nullableName: String? = "Kotlin"
nullableName = null  // 没问题

4.4.2 安全调用操作符

当你操作一个可空变量时,Kotlin 强制你处理空的情况。最常用的就是 ?. 操作符:

val length = nullableName?.length  // 如果 nullableName 为 null,length 也为 null

如果你需要提供一个默认值,可以用 ?:(Elvis 操作符):

val length = nullableName?.length ?: 0  // 如果为 null,返回 0

4.4.3 非空断言

如果你确定一个可空变量不为 null,可以用 !! 操作符:

val length = nullableName!!.length  // 如果为 null,抛出 NullPointerException
警告: 我建议你尽量少用 !!。它本质上是在告诉编译器「别管我,我知道我在做什么」。但说实话,很多时候我们并不确定。我在代码审查时,看到 !! 都会特别留意,因为它往往是潜在 bug 的来源。

4.5 集合框架:List、Set、Map

Kotlin 的集合框架在 Java 的基础上做了大量改进。最核心的一点是:Kotlin 区分了可变集合和不可变集合。

4.5.1 List:有序集合

// 不可变 List
val immutableList = listOf("苹果", "香蕉", "橘子")

// 可变 List
val mutableList = mutableListOf("苹果", "香蕉", "橘子")
mutableList.add("葡萄")
mutableList.remove("香蕉")

我个人习惯:在不需要修改集合内容时,尽量使用不可变集合。这样可以避免意外的修改,让代码更安全。

4.5.2 Set:无序不重复

val set = setOf(1, 2, 3, 3, 2, 1)  // 实际上只包含 1, 2, 3
println(set)  // 输出:[1, 2, 3]

Set 在去重场景下非常有用。我在做数据清洗时经常用到它。

4.5.3 Map:键值对

val map = mapOf(
    "key1" to "value1",
    "key2" to "value2"
)

// 可变 Map
val mutableMap = mutableMapOf(
    "name" to "小明",
    "age" to "18"
)
mutableMap["gender"] = "男"

Kotlin 的 to 中缀函数让 Map 的创建看起来非常自然。我第一次看到这种写法时,就觉得比 Java 的 put 优雅多了。

4.5.4 集合的常用操作

Kotlin 为集合提供了丰富的函数式 API:

val numbers = listOf(1, 2, 3, 4, 5, 6)

// 过滤
val evenNumbers = numbers.filter { it % 2 == 0 }  // [2, 4, 6]

// 映射
val squared = numbers.map { it * it }  // [1, 4, 9, 16, 25, 36]

// 分组
val grouped = numbers.groupBy { if (it % 2 == 0) "偶数" else "奇数" }
// {"奇数": [1, 3, 5], "偶数": [2, 4, 6]}

// 排序
val sorted = numbers.sortedDescending()  // [6, 5, 4, 3, 2, 1]

这些操作链式调用起来非常流畅。我在处理网络返回的数据时,经常用 filter + map 的组合来清洗和转换数据。

核心要点: Kotlin 的集合操作符是惰性求值的。如果你处理的数据量很大,可以考虑使用 Sequence(序列)来避免创建中间集合,提高性能。

4.6 本章知识体系

下面这张图总结了本章的核心知识点和它们之间的关系:

Kotlin 语言基础(下)知识体系 函数与Lambda 默认参数 单表达式函数 Lambda 语法 高阶函数 面向对象编程 类与构造器 继承(open) 接口与默认实现 override 空安全机制 可空类型 ? 安全调用 ?. Elvis 操作符 ?: 非空断言 !! 集合框架 List(有序) Set(去重) Map(键值对) 函数式 API 核心设计理念 简洁:用更少的代码表达更多的逻辑 安全:从语言层面消除空指针异常 互操作:与 Java 100% 兼容,平滑迁移

这四个部分——函数与 Lambda、面向对象、空安全、集合框架——是 Kotlin 开发的核心基础。你想想看,在 Android 开发中,你写的每一行代码几乎都离不开这些概念。掌握好它们,后面的路就好走了。

嗯,这一章的内容就到这里。记住,多写代码才是最好的学习方式。打开你的 IDE,把上面的例子都敲一遍,感受一下 Kotlin 的语法糖和设计哲学。相信我,你会爱上它的。

公众号:蓝海资料掘金营,微信deep3321