Kotlin学习笔记8——内联函数

前言

上一篇,我们学习了Kotlin中的高阶函数和Lambda表达式,今天继续来学习Kotlin中的函数。由于Kotlin中支持高阶函数语法,所以函数我们分为三篇来学习,今天是第三篇:内联函数。

定义

被inline标记的函数就是内联函数,示例:

public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
    
    
    var sum: Int = 0
    for (element in this) {
    
    
        sum += selector(element)
    }
    return sum
}

内联函数好处

调用一个方法其实就是一个方法压栈和出栈的过程,调用方法时将栈帧压入方法栈,然后执行方法体,方法结束时将栈帧出栈,这个压栈和出栈的过程是一个耗费资源的过程,这个过程中传递形参也会耗费资源。我们在写代码的时候难免会遇到这种情况,就是很多处的代码是一样的,于是乎我们就会抽取出一个公共方法来进行调用,这样看起来就会很简洁;但是也出现了一个问题,就是这个方法会被频繁调用,就会很耗费资源。请看示例:

fun <T> method(lock: Lock, body: () -> T): T {
    
    
        lock.lock()
        try {
    
    
            return body()
        } finally {
    
    
            lock.unlock()
        }
    }

这里的method方法在调用的时候是不会把形参传递给其他方法的,调用一下:

 method(lock, {
    
    "我是body的方法体"})//lock是一个Lock对象

对于编译器来说,调用method方法就要将参数lock和lambda表达式{“我是body的方法体”}进行传递,就要将method方法进行压栈出栈处理,这个过程就会耗费资源。如果是很多地方调用,就会执行很多次,这样就非常消耗资源了,于是乎就引进了inline。

inline关键字

那么使用inline关键字标记函数后,在编译时期,把调用这个函数的地方用这个函数的方法体进行替换。还是以上面的method函数为例,再使用inline关键字标记method函数后,我们调用上面的method方法:

method(lock, {
    
    "我是body方法体"})//lock是一个Lock对象

其实上面调用的方法,在编译时期就会把下面的内容替换到调用该方法的地方,这样就会减少方法压栈,出栈,进而减少资源消耗,从而提升了性能。

lock.lock()
try {
    
    
  return "我是body方法体"
} finally {
    
    
  lock.unlock()
}

noinline关键字

虽然内联非常好用,但是会出现这么一个问题,就是内联函数的参数无法被内联函数方法体中的其他非内联函数调用。

inline fun <T> mehtod(lock: Lock, body: () -> T): T {
    
    
            lock.lock()
            try {
    
    
                otherMehtod(body)//会报错,无法调用
                return body()
            } finally {
    
    
                lock.unlock()
            }
    }

    fun <T> otherMehtod(body: ()-> T){
    
    

    }

因为method是内联函数,所以它的形参body就是inline的,但是在编译时期,body已经不是一个函数对象,而是一个具体的值,然而otherMehtod却要接收一个body的函数对象,所以就编译不通过了
解决方法:当然就是加noinline了,它的作用就已经非常明显了.就是让内联函数的形参函数不是内联的,保留原有的函数特征。

看示例:

//body函数调用开销大,需要被内联,body1需要被内联函数方法体中的其他非内联函数调用,不需要内联,用noinline 关键字修饰
inline fun <T> mehtod(lock: Lock, body: () -> T,noinline body1: () -> T): T {
    
    
    lock.lock()
    try {
    
    
        otherMehtod(body)
        return body1()
    } finally {
    
    
        lock.unlock()
    }
}

fun <T> otherMehtod(body: ()-> T){
    
    

}

crossinline关键字

crossinline 的作用是内联函数中让被标记为crossinline 的lambda表达式不允许非局部返回。
首先我们来看下非局部返回
我们都知道,kotlin中,如果一个函数中,存在一个lambda表达式,在该lambda中不支持直接通过return退出该函数的,只能通过return@XXXinterface这种方式

fun outterFun() {
    
    
    innerFun {
    
    
        //return  //错误,不支持直接return
        //只支持通过标签,返回innerFun
        return@innerFun 1
    }

    //如果是匿名或者具名函数,则支持
    var f = fun(){
    
    
        return
    }
}

fun innerFun(a: () -> Int) {
    
    }

但是如果这个函数是内联的就可以返回

fun outterFun() {
    
    
    innerFun {
    
    
        return  //支持直接返回outterFun       
    }
}

inline fun innerFun(a: () -> Int) {
    
    }

这种直接从lambda中return掉函数的方法就是非局部返回,crossinline就是为了让其不能直接return。

fun outterFun() {
    
    
    innerFun {
    
    
        return  //这样就报错了
    }
}

inline fun innerFun( crossinline a: () -> Int) {
    
    }

这里的a函数就是被crossinline修饰了,如果在lambda中直接return就无法编译通过。说白了,我们如果直接在lambda参数中结束当前函数,而不给lambda提供一个返回值,这种情况是不被允许的。

reified关键字

reified,字面意思:具体化,其实就是具体化泛型。我们都知道在java中如果是泛型,是不能直接使用泛型的类型的,但是kotlin却是可以的,这点和java就有了显著的区别.通常java中解决的方案就是通过函数来传递类但是kotlin就老牛逼了,直接就可以用了,主要还是有内联函数inline这个好东西,才使得kotlin能够直接通过泛型就能拿到泛型的类型。

看示例:

 inline fun <reified T : Activity> Activity.startActivity() {
    
    
     startActivity(Intent(this, T::class.java))
}

通过kotlin的拓展写个启动activity的方法,只需要传入该activity的泛型即可

startActivity<MainActivity>()

再来看看一个需求,有的时候我们需要创建一个Fragment的实例,并且要传递参数,如果是之前你可能会在每个Fragment里面这样写:

 fun newInstance(param: Int): ActyFragment {
    
    
            val fragment = ActyFragment()
            val args = Bundle()
            args.putInt(PARAMS, param)
            fragment.arguments = args
            return fragment
        }

现在通过reified来优化:

inline fun <reified F : Fragment> Context.newFragment(vararg args: Pair<String, String>): F {
    
    
    val bundle = Bundle()
    args.let {
    
    
        for (arg in args) {
    
    
            bundle.putString(arg.first, arg.second)
        }
    }
    return Fragment.instantiate(this, F::class.java.name, bundle) as F
}

这样就不要每个Fragment都写个方法了,可以说非常的nice了。

尾巴

今天的学习笔记就先到这里了,下一篇我们将学习Kotlin中的类和对象
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

猜你喜欢

转载自blog.csdn.net/abs625/article/details/106886051