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"。因为扩展函数是根据变量的声明类型来决定的,不是根据实际类型。这就是静态解析。
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最实用的特性之一,也是后面高阶函数和DSL的基础。你先把这些基础打牢,后面咱们再聊更高级的玩法。
公众号:蓝海资料掘金营,微信 deep3321