【疯狂Android之Kotlin】继续谈谈Kotlin的扩展函数

1,438 阅读5分钟

扩展函数Kotlin


扩展函数是 Kotlin 用于简化一些代码的书写产生的,其中有 let、with、run、apply、also 五个函数

let 函数

在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式

一般写法

fun main() {
    val text = "RainyJiang"
    println(text.length)
    val result = 1000
    println(result)
}

let 写法

fun main() {
    val result = "RainyJiang".let {
        println(it.length)
        1000
    }
    println(result)
}

最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理

mVideoPlayer?.setVideoView(activity.course_video_view)
mVideoPlayer?.setControllerView(activity.course_video_controller_view)
mVideoPlayer?.setCurtainView(activity.course_video_curtain_view)
mVideoPlayer?.let {
   it.setVideoView(activity.course_video_view)
   it.setControllerView(activity.course_video_controller_view)
   it.setCurtainView(activity.course_video_curtain_view)
}

又或者是需要去明确一个变量所处特定的作用域范围内可以使用

with 函数

前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式

定义 Person 类

class Person(var name : String, var age : Int)

一般写法

fun main() {
    var person = Person("RainyJiang", 100)
    println(person.name + person.age)
    var result = 1000
    println(result)
}

with 写法

fun main() {
    var result = with(Person("RainyJiang", 100)) {
        println(name + age)
        1000
    }
    println(result)
}

适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上

override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年龄:${item.age}"
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    with(item){
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年龄:$age"
    }
}

run 函数

实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式

一般写法

var person = Person("RainyJiang", 100)
println(person.name + "+" + person.age)
var result = 1000
println(result)

run 写法

var person = Person("RainyJiang", 100)
var result = person.run {
    println("$name + $age")
    1000
}
println(result)

适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理,这里还是借助 onBindViewHolder 案例进行简化

override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    holder.nameView.text = "姓名:${item.name}"
    holder.ageView.text = "年龄:${item.age}"
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
    val item = getItem(position)?: return
    item?.run {
        holder.nameView.text = "姓名:$name"
        holder.ageView.text = "年龄:$age"
    }
}

apply 函数

从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身

一般写法

val person = Person("RainyJiang", 100)
person.name = "ZJX"
person.age = 50

apply 写法

val person = Person("RainyJiang", 100).apply {
    name = "ZJX"
    age = 50
}

整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。正是基于这一点差异它的适用场景稍微与run函数有点不一样。apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。特别是在我们开发中会有一些数据model向View model转化实例化的过程中需要用到

mRootView = View.inflate(activity, R.layout.example_view, null)
mRootView.tv_cancel.paint.isFakeBoldText = true
mRootView.tv_confirm.paint.isFakeBoldText = true
mRootView.seek_bar.max = 10
mRootView.seek_bar.progress = 0

使用 apply 函数后的代码是这样的

//动态inflate出一个view的XML
mRootView = View.inflate(activity, R.layout.example_view, null).apply {
   tv_cancel.paint.isFakeBoldText = true
   tv_confirm.paint.isFakeBoldText = true
   seek_bar.max = 10
   seek_bar.progress = 0
}

多层级判空问题

if (mSectionMetaData == null || mSectionMetaData.questionnaire == null || mSectionMetaData.section == null) {
    return;
}
if (mSectionMetaData.questionnaire.userProject != null) {
    renderAnalysis();
    return;
}
if (mSectionMetaData.section != null && !mSectionMetaData.section.sectionArticles.isEmpty()) {
    fetchQuestionData();
    return;
}

kotlin的apply函数优化

mSectionMetaData?.apply {

    //mSectionMetaData不为空的时候操作mSectionMetaData

}?.questionnaire?.apply {

    //questionnaire不为空的时候操作questionnaire

}?.section?.apply {

    //section不为空的时候操作section

}?.sectionArticle?.apply {

    //sectionArticle不为空的时候操作sectionArticle

}

also 函数

also函数的结构实际上和let很像唯一的区别就是返回值的不一样,let是以闭包的形式返回,返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身

fun main() {
    val result = "RainyJiang".let {
        println(it.length)
        1000
    }
    println(result) // 打印:1000
}
fun main() {
    val result = "RainyJiang".also {
        println(it.length)
    }
    println(result) // 打印:RainyJiang
}

适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用

总结

通过以上几种函数的介绍,可以很方便优化kotlin中代码编写,整体看起来几个函数的作用很相似,但是各自又存在着不同。使用的场景有相同的地方比如run函数就是let和with的结合体

最后附上测试代码

class ExpandFunction {


    fun letFunction() {
        //在函数内可以通过it指代该对象
        //返回值为函数块的最后一行或指定return表达式
        /*
        val text ="HelloWorld"
        println(text)
        val result = 1000
        println(result)
        */

        val result = "Hello World".let {
            println(it.length)
            1000
        }
        println(result)
    }

    fun withFunction() {
   //调用同一个类的多个方法时,可以省去类名重复,可以使用with函数
        var result = with(Person("Jacky",19)) {
            println(name+age)
            1000
        }
    }

    fun runFunction() {
        //结合let和with,适用于let和with的任意场景
        var person = Person("RainyJiang", 100)
        var result = person.run {
            println("$name + $age")
            1000
        }
        println(result)
    }

    fun applyFunction() {
        //run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身
        val person = Person("Jacky",19)
        var result = person.apply {
            name = "tallow"
            age = 18
        }

        //另外apply行数还可以用于层级判空
    }

    fun alsoFunction() {

        //also函数和let函数很像let是以闭包的形式返回,
        // 返回函数体内最后一行的值,如果最后一行为空就返回一个Unit类型的默认值。而also函数返回的则是传入对象的本身
        val result = "Jacky".also {
            println(it.length)
        }
        println(result)
    }
}

class Person(var name: String, var age: Int)