Kotlin基础回顾:空安全、扩展函数、高阶函数与Lambda表达式

好,咱们直接进入正题。这一章我打算聊聊Kotlin里几个最基础、但也是最核心的特性。说实话,很多从Java转过来的同学,一开始会觉得这些语法有点「怪」。但用久了你会发现,这些设计是真的香。

1. 空安全:别再让NullPointerException背锅了

先说说空安全。Java里那个经典的NPE,我估计大家都被坑过。我记得有一次线上崩溃,查了半天,就是一个简单的对象没判空。嗯,从那以后我写Java都养成了「万物皆判空」的强迫症。

Kotlin的做法很直接——它在类型系统层面就把空和非空分开了。

// 非空类型
var name: String = "Kotlin"
// name = null  // 编译报错

// 可空类型
var nullableName: String? = "Kotlin"
nullableName = null  // 没问题

你想想看,这相当于在编译期就帮你堵住了大部分空指针的坑。我个人习惯是:能用非空类型就绝不用可空类型。实在避免不了,再用安全调用操作符 ?.

核心操作符速查表

操作符 含义 示例
?. 安全调用 str?.length
?: Elvis 操作符 str?.length ?: 0
!! 非空断言 str!!.length

避坑指南:我曾经在代码里滥用 !!,觉得「这里肯定不为空」。结果上线后啪啪打脸。现在我的原则是:!! 只用在明确知道不会为空的场景,比如从Bundle取数据且你确定之前存过。其他情况,老老实实用 ?.?:

2. 扩展函数:给别人的类加点「私货」

扩展函数这个特性,说白了就是让你能给一个已经存在的类添加新方法,又不用继承它。这在Android开发里特别实用。

举个例子,我们经常需要把dp转成px。以前在Java里,你得写个工具类,然后到处调用 DensityUtil.dp2px(context, 16f)。在Kotlin里,你可以这样:

fun Int.dp2px(context: Context): Int {
    return (this * context.resources.displayMetrics.density).toInt()
}

// 使用
val px = 16.dp2px(context)

你看,直接给 Int 类型加了个方法。我个人觉得,扩展函数最大的价值在于:它让代码读起来更像「自然语言」。我在项目中经常用扩展函数来封装一些重复的UI操作,比如给View设置可见性:

fun View.visible() {
    visibility = View.VISIBLE
}

fun View.gone() {
    visibility = View.GONE
}

// 使用
button.visible()
textView.gone()

小技巧:扩展函数是静态解析的,不是运行时动态分发的。也就是说,它本质上就是个静态方法。所以别指望它能实现多态效果。这一点我刚开始学的时候也迷糊过。

3. 高阶函数与Lambda:把函数当参数传

高阶函数,说白了就是「能接收函数作为参数,或者能返回一个函数」的函数。Lambda则是那种「匿名函数」的简洁写法。

为什么会需要这个?你想想看,在Java里我们写点击事件,得搞个匿名内部类。代码又长又啰嗦。Kotlin里一个Lambda就搞定了。

// Java 写法
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // do something
    }
});

// Kotlin Lambda 写法
button.setOnClickListener { v ->
    // do something
}

这还不是最爽的。高阶函数真正的威力在于「抽象控制流程」。比如我们经常需要做「在IO线程执行,在主线程回调」的操作:

fun <T> asyncTask(
    onBackground: () -> T,
    onMain: (T) -> Unit
) {
    thread {
        val result = onBackground()
        runOnUiThread {
            onMain(result)
        }
    }
}

// 使用
asyncTask(
    onBackground = {
        // 网络请求或数据库操作
        "result"
    },
    onMain = { result ->
        // 更新UI
        textView.text = result
    }
)

我在项目中重构过一个网络请求模块,就是用这种高阶函数把「异步+回调」的模式封装起来的。代码量直接砍了一半,而且逻辑清晰了很多。

Lambda语法要点

  • 如果Lambda是最后一个参数,可以提到括号外面
  • 如果只有一个参数,可以用 it 隐式表示
  • 如果Lambda是唯一参数,括号都可以省略
// 完整写法
list.filter({ item -> item.length > 3 })

// 简化写法
list.filter { it.length > 3 }

4. 实战:把这些特性串起来

光讲理论没意思。我拿一个实际场景来演示一下——给RecyclerView的Adapter写一个通用的点击事件处理。

// 扩展函数:给ViewHolder添加点击能力
fun <T> RecyclerView.ViewHolder.onItemClick(
    item: T?,
    action: (T) -> Unit
) {
    itemView.setOnClickListener {
        item?.let { nonNullItem ->
            action(nonNullItem)
        }
    }
}

// 使用
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position)
        holder.onItemClick(item) { clickedItem ->
            // 这里处理点击事件
            Toast.makeText(context, clickedItem.name, Toast.LENGTH_SHORT).show()
        }
    }
}

你看,这里用到了扩展函数、空安全(?.let)、高阶函数和Lambda。几个特性一组合,代码既简洁又安全。

个人经验:刚开始学Kotlin的时候,别急着把所有代码都改成Lambda。我建议先写完整写法,等熟悉了再逐步简化。不然一上来就各种 it、省略括号,自己都容易看晕。

好了,这一章的内容就到这里。空安全、扩展函数、高阶函数和Lambda,这几个概念是Kotlin的基石。理解透了,后面学协程、Flow这些高级特性会轻松很多。下一章我们聊聊集合操作符,那个也是Kotlin的拿手好戏。