Kotlin 扩展函数
1. 为什么需要扩展函数?
我们都知道 Koltin 这门语言与 Java 有非常好的互操作性,并且扩展函数这个新特性可以很平滑与现有Java 代码集成。甚至纯 Kotlin 的项目都可以基于 Java 库或者 Android 中的一些第三方框架库来构建,所以扩展函数非常适合 Kotlin 和 Java 语言混合开发模式。在很多公司一些比较稳定良好的库都是 Java 开发的,也完全没必要去用 Kotlin 语言重写。但是想要扩展库的接口和功能,这时候扩展函数可能就会派上用场。使用 Kotlin 的扩展函数还有一个好处就是没有副作用,不会对原有库代码或功能产生影响。
先来一个扩展函数的例子一睹为快,来实现一个给 Android 中 TextView 组件设置加粗的扩展函数:
//扩展函数的定义
fun TextView.isBold() = this.apply {
paint.isFakeBoldText = true
}
//扩展函数的调用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()
2. 扩展函数的语法
上面例子不难看出,Kotlin 的扩展函数是真的强大,可以毫无副作用给原有库的类增加属性和方法,比如例子中 TextView,我们根本没有去动 TextView 源码,但是却给它增加一个扩展函数。具有那么强大功能,到底它背后原理是什么呢?下面我们来深入学习下它的语法以及语法背后的原理。
2.1 扩展函数的定义
Kotlin 可以为一个不能修改的或来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用,这种机制的函数称为扩展函数。
//扩展函数定义
fun TextView.isBold() = this.apply {
paint.isFakeBoldText = true
}
2.2 扩展函数的分析
扩展函数只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用 .
调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的函数或属性。
2.3 扩展函数的背后原理
扩展函数实际上就是一个对应 Java 中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的:
//这个类名就是顶层文件名+“Kt”后缀
public final class ExtendsionTextViewKt {
@NotNull
public static final TextView isBold(@NotNull TextView $receiver) {//扩展函数isBold对应实际上是Java中的静态函数,并且传入一个接收者类型对象作为参数
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
$receiver.getPaint().setFakeBoldText(true);//设置加粗
return $receiver;//最后返回这个接收者对象自身,以致于我们在Kotlin中完全可以使用this替代接收者对象或者直接不写。
}
}
3. 扩展函数的使用场景
扩展函数的使用场景主要就是毫无副作用给原有库的类增加函数,增强类的行为。比如 Android 中给所有 View 组件增加常见动画、给 ImageView 组件增加一个加载网络图片函数、给SpannableStringBuilder
增加 bold
加粗、italic
斜体、color
颜色等一系列 Span
的设置以及很多需要重复设置模板代码场景都可以使用扩展函数。
4. 扩展函数的使用示例
示例1: Android 中给 ImageView 组件增加一个 loadUrl 的扩展函数,不再需要繁琐 Glide 的调用,只需简单一行 loadUrl 代码的调用就能实现加载图片功能:
//loadUrl扩展函数接收者类型是ImageView
fun ImageView.loadUrl(requestManager: RequestManager = Glide.with(context)
, url: String = ""
, urls: List<String> = listOf(url)
) {
requestManager.view(this).url(urls).start()//this指代的是ImageView这个接收者对象实例, 这里this也可以省略
}
//使用扩展函数来加载多张图片的调用方式
course_cover_iv1.loadUrl("https://www.imooc.com/test1.png")//imageView直接调用loadUrl像是给ImageView增加一个网络图片加载函数一样,调用非常简单。
course_cover_iv2.loadUrl("https://www.imooc.com/test2.png")
course_cover_iv3.loadUrl("https://www.imooc.com/test3.png")
//不使用扩展函数来加载多张图片的调用方式
Glide.with(context)
.view(course_cover_iv1)
.url("https://www.imooc.com/test1.png")
.start()
Glide.with(context)
.view(course_cover_iv2)
.url("https://www.imooc.com/test2.png")
.start()
Glide.with(context)
.view(course_cover_iv3)
.url("https://www.imooc.com/test3.png")
.start()
示例2: Android 中给 View 组件增加一个 animAlpha 透明动画的扩展函数,不需要每次都去重新写属性动画的配置,只需要简单一行代码调用 View 中的方法即可。像是给原本就不具备透明动画 View 组件无缝地提供一个动画的功能:
//animAlpha扩展函数接收者类型是View,也就是View的子类都可以直接使用animAlpha函数
fun View.animAlpha(
duration: Long = 600,
alpha: Float = 0f,
delay: Long = 0,
animatorEnd: ((Animator?) -> Unit)? = null
) {
this.animate()//this指代的是View这个接收者对象实例, 这里this也可以省略
.setDuration(duration)
.setInterpolator(AccelerateDecelerateInterpolator())
.setStartDelay(delay)
.alpha(alpha)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
animatorEnd?.invoke(animation)
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
.start()
}
//animAlphaVisible扩展函数,设置View组件的显示和消失透明度动画,通过isVisible控制
fun View.animAlphaVisible(
isVisible: Boolean,
duration: Long = 600,
animatorEnd: ((Animator?) -> Unit)? = null
) {
//扩展函数内部再调用扩展函数animAlpha
animAlpha(duration, alpha = if (isVisible) 1f else 0f, animatorEnd = animatorEnd)
}
//扩展函数animAlpha调用
mEditText.animAlpha(alpha = 0.2f)//EditText组件通过最开始透明度变化到alpha为0.2f
//扩展函数animAlphaVisible调用
mIv.animAlphaVisible(isVisible = true)//ImageView组件设置透明度显示的动画
mTv.animAlphaVisible(isVisible = false)//TextView组件设置透明度消失的动画
5. 扩展函数需要注意的点
-
扩展函数不能像成员函数那样被子类重写;
-
扩展函数不能访问原始类中私有成员属性或成员函数;
-
扩展函数的本质通过传入对象实例,委托对象来访问成员函数,所以它的访问权限和对象访问权限一致。
6. 扩展函数与成员函数的区别
我们通过上述扩展函数的学习都知道,扩展函数在外部调用方式来看和类的成员函数是一致,但是两者却有着本质的区别。
- 扩展函数和成员函数使用方式类似,可以直接访问被扩展类的方法和属性。(原理: 传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性);
- 扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性。(原理: 原理很简单,扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了);
- 扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数;
- 父类成员函数可以被子类重写,而扩展函数则不行。
7. 扩展函数使用经验
虽然扩展函数功能十分强大,但是记住千万不要滥用扩展函数,并不是所有场景定义都合适定义成扩展函数。定义成扩展函数需要抓住以下三个点:
-
第一,这个函数需要被很多地方复用,定义成扩展函数减少很多重复模板代码;
-
第二,这个函数具有扩展的原始类,如果没有那么就不适合定义成扩展函数,它经常很容易会和顶层函数使用场景弄混;
-
第三,如果需要扩展一个原始类的行为时候用扩展函数,如果是扩展一个原始类的状态的时候建议使用扩展属性。
8. 总结
扩展函数的本质是一个对应 Java 中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。另外,扩展函数与成员函数的相同点和不同点也是扩展函数中的重点。