首发于公众号: DSGtalk1989
20.内联函数
-
关键字inline
高阶函数需要传入的函数参数最终都会通过对象的方式去使用,而为了提升性能,我们需要使用
inline关键字去修饰内联函数,内联函数可以直接将方法体编译至调用处。//kotlin fun notInlineFun(){ val a = 1L val b = "123" } inline fun inlineFun(){ val c = 2L val d = "456" } fun main() { notInlineFun() inlineFun() } //decompiled public final class InlineTestKt { public static final void notInlineFun() { long a = 1L; String b = "123"; } public static final void inlineFun() { int $i$f$inlineFun = 0; long c = 2L; String d = "456"; } public static final void main() { notInlineFun(); int $i$f$inlineFun = false; long c$iv = 2L; String var3 = "456"; } // $FF: synthetic method public static void main(String[] var0) { main(); } }我们可以看到,
inlineFun中的函数内容全部被编译复制到了main方法中。
-
到底什么时候内联
我们在上面的例子中,会发现kotlin给出了一个提示
expected performance impact of inlining is insignificant. Inlining works best for functions with parameters of functional types
直接这样使用
inline对于性能的影响微乎其微,建议将inline和lambda表达式结合起来用,即官方建议我们将内联函数使用在高阶函数中。那么是否我们所有的高阶函数为了提高性能都可以直接使用
inline来进行就修饰了呢,也不尽然。目前看来内联函数由于是代码拷贝的方式,本身提高性能的同时,可以进行代码内
return。首先我们来看一下,没有用inline形容的高阶函数。
fun html( a : (String) -> Unit){ a.invoke("abc") a("def") }如果我们想要跳出的话,需要加入标签才能跳出,并且只是单单针对函数体的返回类型
Unit的函数,同时只能跳出函数体。html { return@html }而比如我们有这样一个,如果给到的
String不是我们需要的那个,我们希望直接跳出包含调用函数的函数体,而不是单单跳出html函数,及如下的效果fun main(){ html { if(it.startWith("a")){ println("failed") //希望此处退出,并且连下面的success也不答应出来 } } println("success") }这时候我们就不得不用内联函数来进行处理,由于内联函数是整个函数的拷贝进入,就是等于把整个lambda函数原封不动的拷贝到调用所在地。所以
return就能跳出调用函数的函数体fun main(){ html { if(it.startsWith("a")){ println("failed") return } } println("success") }目前的kotlin版本中暂时不支持
break和continue -
crossinline和noinline
在高阶函数中,我们用
noinline关键字和crossinline来修饰lambda表达式,首先这两个表达式都不允许进行return,不一样的是noinline是彻底的不进行拷贝,而crossinline依然是拷贝的,只是不允许return使用
inline修饰的高阶函数,默认参数函数都是直接内联拷贝的。
-
实体化类型参数reified
有时候我们只是单单需要使用一下类型,即java中的
classType,比如说如果这个传参是什么类型的话,那我们就做不同的处理。一般情况下我们会这样处理。fun <T> doSomeThing(a : T) : Unit{ if(a is String){ println("is String") } println("is Other") } fun <T> doSomeThing(clazz : Class<T>) : Unit{ if(clazz.isInstance(String)){ println("is String") } println("is Other") }这种情况通常我们在调用的时候都会省去
<>,因为系统都可以帮我们判断出来是什么类型。那么我们再来看一下这样一种情况,类
A中有一个b属性,我们需要看下这个b是否是某个属性。class A{ val b = Any() } fun <T> A.bIsType(clazz : Class<T>){ if(clazz.isInstance(b)){ println("get it") } } fun main() { val a = A() a.bIsType(String::class.java) }我们生成了一个
A的扩展函数,传入一个类型参数,然后判断一下b是不是我们需要的那个类型。换一种方式,我们希望直接可以直接把泛型的类型拿来用,而不是需要传入具体的
Class,这样我们就不需要往里面传入具体的对象了,直接通过<String>的方式,这里我们就需要使用到reified为什么要放在内联函数中介绍呢,因为
reified关键字只会和inline一起出现,修改后为如下inline fun <reified T> A.bIsType(){ if(b is T){ println("get it") } } fun main() { val a = A() a.bIsType<String>() }这样一来,泛型T可以直接当成类型对象来使用,相当的方便。
-
内联属性
属性有
get和set的方法,如果这两个方法中并没有涉及到field的复杂运算,我们也可以将属性相应的用inline进行修饰,我们可以直接修饰属性,或者修饰属性的get或者set方法。val foo: Foo inline get() = Foo() var bar: Bar get() = ... inline set(v) { ... } inline var bar: Bar get() = ... set(v) { ... }具体在什么地方使用,暂时还没有参透。。
Kotlin学习笔记之 13 基础操作符run、with、let、also、apply