首发于公众号: 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