扩展函数一般用于为第三方SDK中的类添加功能方法,是实现多态的一种形式。
Kotlin的扩展函数是“开放-封闭原则”-----对扩展开放,对修改封闭的良好实现。它替代了继承形式的扩展,做到了更优雅的使用和调用,也避免了继承带来的其他问题。
一、编写和使用
自定义扩展函数
-
找到一个合适的定义文件
- 我们一般将一种类型,或者实现同一类方法的的扩展函数,统一定义到一个Kt文件里
与定义普通函数类似,除此之外,扩展函数需要定义“接收者类型”作为前缀,代表该函数的接收方
//形如
fun <class_name>.<method_name>() {
}
//举例
fun <T> MutableList<T>.swap(from: Int, to: Int): Boolean {
if (from < 0 || from >= this.size) {
return false
}
if (to < 0 || to >= this.size) {
return false
}
val temp = this[from]
this[from] = this[to]
this[to] = temp
return true
}
//使用
val list = mutableListOf(1, 2, 3)
list.swap(1, 2)
-
自定义扩展变量
扩展属性实际上是提供一种方法来访问属性,因为不可能给类添加额外的属性字段,只是使用简洁语法类似直接操作属性,实际上还是方法的访问,自然就没有默认get()/set()方法实现,所以必须显式提供get()/set()方法。
var TextView.isBolder: Boolean
get() {
return this.paint.isFakeBoldText
}
错误示例
覆盖已有成员方法
fun Activity.onDestroy() {
//.....
}
同名的类中,成员方法的优先级总高于扩展函数,Kotlin会默认使用成员方法,防止方法调用的混淆。
- 不分场景的滥用
fun Context.loadImage(url: String, imageView: ImageView) {
Glide.with(this)
.load(url)
.placeholder(R.drawable.default)
.error(R.drawable.error)
}
我们为Context类扩展了不属于其使用范畴的功能,仅仅是省去了调用时的部分传参,还可能产生ImageView与当前Context不一致的问题,属于扩展机制的滥用。
应该进行如下修改:
fun ImageView.loadImage(url: String) {
Glide.with(this.context)
.load(url)
.placeholder(R.drawable.default)
.error(R.drawable.error)
}
二、常用标准库扩展函数的使用
let
实现:public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
返回:闭包形式,返回值为最后一行的值或者指定的return的表达式
-
适用场景:
- 明确一个变量所处特定的作用域范围,常用作处理需要针对一个可null的对象统一做判空处理
mArticle?.let {
//it在let作用域内,代表mArticle非空对象
it.todo()
}
- with
with其实不是一个扩展函数,只是一个内联函数
实现:fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
返回:闭包形式,返回值为最后一行的值或者指定的return的表达式
-
使用场景
- 适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法、变量
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val item = getItem(position)?: return
//使用with
with(item){
holder.tvNewsTitle.text = StringUtils.trimToEmpty(title)
holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
}
//不使用with
holder.tvNewsTitle.text = StringUtils.trimToEmpty(item.titleEn)
holder.tvNewsSummary. text = StringUtils.trimToEmpty(item.summary)
}
- run
run函数是let、with两个函数结合体
实现:public inline fun <T, R> T.run(block: T.() -> R): R = block()
返回:闭包形式,返回值为最后一行的值或者指定的return的表达式
-
使用场景
- run函数一方面弥补了let函数在函数体内必须使用it参数替代对象,另一方面它弥补了with函数传入对象判空问题。在run函数中可以像let函数一样做判空处理,也可以像with函数一样可以省略类名,直接访问实例的公有属性和方法。
fun main(args: Array<String>) {
var user :User? = null
val result = user?.run {
println("my name is $name, I'm $age years old)
1000
}
println("result: $result") //result: 1000
}
class User(var name: String, var age: String) {
}
- apply
apply函数与run函数类似,唯一不同点就是它返回的值是对象本身
实现:fun T.apply(block: T.() -> Unit): T { block(); return this }
返回:返回this(当前对象)
-
使用场景
- apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值
fun initUserView(name: String, age: String) {
userView = View.inflate(context, R.layout.layout_user, null).apply {
user_name = name
user_age = age
}
}
- also
also函数与let函数类似,唯一不同点就是它返回的值是对象本身
实现:public inline fun T.also(block: (T) -> Unit): T { block(this); return this }
返回:返回this(当前对象)
-
使用场景
- also函数一般用于多个扩展函数链式调用
val original = "abc"
original.let{
println(it) //abc
it.reversed()
}.let{
println(it) //cba
}
original.also{
println(it) //abc
it.reversed()
}.also{
println(it) //abc
}
//let每次会返回最后一个表达式的结果,而also会返回内置对象。
takeIf
实现:public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
返回:返回this或null
-
使用场景:
- 多重判空
//Java
if(someObject!= null && someObject.status){
someObject.doThis()
}
//Kotlin
someObject?.takeIf {status}?doThis()