1. Kotlin扩展函数:给Android类“开外挂”

说实话,我第一次接触Kotlin扩展函数时,心里想的是:“这不就是语法糖吗?”但用了一段时间后,我彻底改变了看法。它不仅仅是糖,它是真正能改变你编码方式的东西。

咱们先看一个场景。你写Android的时候,是不是经常这样:

// 传统写法
val toast = Toast.makeText(context, "Hello", Toast.LENGTH_SHORT)
toast.show()

每次都要写三行,烦不烦?用扩展函数,一行搞定:

// 扩展函数版本
context.toast("Hello")

嗯,这就是扩展函数的魅力。它让你能给已有的类添加新方法,哪怕这个类是别人写的,甚至是系统自带的。

1.1 扩展函数的定义与语法

语法其实很简单。你只需要在函数名前加上“接收者类型”和一个点号:

fun 接收者类型.函数名(参数列表): 返回类型 {
    // 函数体
}

举个例子:

fun String.isEmail(): Boolean {
    return this.contains("@") && this.contains(".")
}

// 使用
val email = "test@example.com"
println(email.isEmail()) // true

注意看,this指向的就是调用这个函数的对象。在这个例子里,this就是那个字符串本身。

我的习惯:扩展函数名要起得直观。我一般用动词开头,比如showToast()loadImage(),一看就知道是干嘛的。

1.2 扩展函数的作用域

扩展函数不是随便写的。你得把它放在合适的地方。主要有三种:

  • 顶层函数:直接写在文件里,不放在任何类中。全局可用。
  • 成员扩展:定义在某个类内部,只在该类的作用域内可用。
  • 局部扩展:定义在函数内部,只在那个函数里有效。

我建议你把常用的扩展函数放在一个单独的Extensions.kt文件里。这样好找,也好管理。

// Extensions.kt
fun Context.showToast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

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

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

你看,这样写完之后,你在任何Activity或Fragment里都能直接调showToast("成功"),不用再写那一大串了。

1.3 扩展函数与成员函数的区别

这里有个坑,我得提醒你。扩展函数和成员函数看起来很像,但本质不同。

对比项 成员函数 扩展函数
定义位置 类内部 类外部
访问私有成员 可以 不可以
继承/重写 支持 不支持(静态解析)
调用方式 直接调用 看起来像成员调用

我曾经在项目里犯过一个错:我写了一个扩展函数,以为它能像成员函数一样被重写。结果发现,子类调用时还是调了父类的扩展函数。为什么?因为扩展函数是静态解析的。

1.4 扩展函数的静态解析

这个知识点很重要。扩展函数不是真正的成员函数。它不会参与多态。

什么意思呢?看代码:

open class Animal
class Dog : Animal()

fun Animal.speak() = "Animal speaks"
fun Dog.speak() = "Dog barks"

fun main() {
    val animal: Animal = Dog()
    println(animal.speak()) // 输出:Animal speaks
}

你猜输出什么?是"Animal speaks"。因为扩展函数是根据变量的声明类型来决定的,不是根据实际类型。这就是静态解析。

避坑指南:我曾经在项目里用扩展函数给View添加了一个setOnClickListener的变体,结果发现它不会像真正的成员函数那样被重写。如果你需要多态行为,别用扩展函数,用成员函数或者接口。

1.5 为Android类添加扩展函数

这才是重头戏。Android开发中,扩展函数能帮你省下大量重复代码。

先看Context:

fun Context.dp2px(dp: Float): Float {
    return dp * resources.displayMetrics.density
}

fun Context.px2dp(px: Float): Float {
    return px / resources.displayMetrics.density
}

// 使用
val height = context.dp2px(48f)

再看View:

fun View.onClick(block: () -> Unit) {
    setOnClickListener { block() }
}

// 使用
button.onClick {
    showToast("点击了按钮")
}

Activity也可以:

fun Activity.startActivity(clazz: Class<*>) {
    startActivity(Intent(this, clazz))
}

// 使用
startActivity(DetailActivity::class.java)

这些扩展函数写一次,整个项目都能用。我现在的项目里,光View相关的扩展函数就有二十多个。从visible()gone()loadImage()setDebounceClick(),全是扩展函数。

1.6 扩展属性

除了函数,属性也能扩展。不过要注意,扩展属性不能有幕后字段(backing field)。

val View.isVisible: Boolean
    get() = visibility == View.VISIBLE

val Context.screenWidth: Int
    get() = resources.displayMetrics.widthPixels

// 使用
if (view.isVisible) {
    // 处理逻辑
}
val width = context.screenWidth

扩展属性本质上就是getter/setter的语法糖。你不能给它赋值,因为它没有真正的字段存储数据。

我的建议:扩展属性适合那些计算一次就能得到的值,比如屏幕宽度、状态栏高度。每次调用都重新计算的那种,用扩展函数更合适。

知识体系总览

下面这张图帮你理清扩展函数的核心脉络:

Kotlin扩展函数 定义与语法 作用域 与成员函数区别 静态解析 Android实战 扩展属性 核心:静态解析 · 不能访问私有成员 · 不能重写

扩展函数这东西,说白了就是给别人的类“加方法”。你不需要继承,不需要修改源码,直接在外面加。我刚开始用的时候还有点不习惯,总觉得这样写是不是太“野”了。但用久了你会发现,它让代码变得特别干净。

记住一点:扩展函数是工具,不是银弹。该用的时候用,不该用的时候别硬上。比如你想给一个类添加状态,那还是老老实实用成员变量或者委托吧。

好了,这一章的内容就到这里。扩展函数是Kotlin最实用的特性之一,也是后面高阶函数和DSL的基础。你先把这些基础打牢,后面咱们再聊更高级的玩法。


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