本文基于Kotlin 1.7.0版别,现在Kotlin最新版别为1.8.22
信任大家在日常运用Kotlin的进程中,现已触摸了许多inline函数,包括源码中也有许多许多办法运用了inline来修改某些办法,不知道是不是有种疑问,一个办法明明能够直接调用,为啥非要用inline来润饰呢?inline修改的办法参数中,居然还有noinline和crossinline关键字来润饰lambda。下面来详细说明下这三个关键字的作用和运用场景。
inline内联
被inline润饰的办法叫做内联函数,它润饰的办法需求接收了一个或多个lambda表达式作为参数,
假如此办法参数没有lambda表达式,那么编译器将提醒你Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types,这个正告说明:此内联对功能的影响很小、微乎其微,内联适合的是具有函数类型的参数,所以编译器觉得此办法不适用于inline润饰。
咱们来经过inline和非inline函数对比一下它的作用,两个办法完成相同的功用,然后看下最终编译器是如何调用两种函数。
// 此函数为内联函数,传入一个lambda表达式
inline fun inlineTest(block: () -> Unit) {
println("inlineTest")
block()
}
// 此函数为非内联函数,也是传入一个lambda表达式
fun test(block: () -> Unit) {
println("test")
block()
}
两个函数除了inline关键字润饰不同意外,其他都是共同的,接下来看看调用之后的输出是否共同,此处咱们加上了测量函数履行时间的功用,以此更直观的观察inline带来的作用。
fun main() {
println(measureTimeMillis {
inlineTest {
println("main inlineTest")
}
})
println(measureTimeMillis {
test {
println("main test")
}
})
}
#
inlineTest
main inlineTest
1
test
main test
9
inline函数履行时间为1,非inline函数履行时间为9
两个函数履行的作用是共同的,但是从输出日志中能够暗处,inline函数的履行时间要显着比非inline函数少,这也便是为什么官方推荐咱们在有lambda参数的时候加上inline使其变为内联函数,那么inline究竟有了什么魔方能够削减了函数调用的开支呢,下面经过将class反编译成Java来看看函数的详细调用。
public final class MainKt {
public static final void main() {
// ①此处为inline函数的调用进程
long start$iv = System.currentTimeMillis();
System.out.println("inlineTest");
System.out.println("main inlineTest");
long var6 = System.currentTimeMillis() - start$iv;
System.out.println(var6);
// ②此处为非inline函数的调用进程
start$iv = System.currentTimeMillis();
test((Function0)null.INSTANCE);
var6 = System.currentTimeMillis() - start$iv;
System.out.println(var6);
}
// ③在编译器中能够看到此处inline函数并没有调用的当地
public static final void inlineTest(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
int $i$f$inlineTest = false;
System.out.println("inlineTest");
block.invoke();
}
public static final void test(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
System.out.println("test");
block.invoke();
}
// $FF: synthetic method
public static void main(String[] args) {
main();
}
}
在反编译的代码中标记了三处当地,顺着这三处就能够清晰的看出inline函数和常规函数的不同之处。
榜首处①是inline函数的详细调用进程,从代码中能够看出,此处并没有直接调用inlineTest()这个办法,反而是直接将函数的内容拷贝到调用途,并且将lambda中的代码也一并拷贝过来了,直接削减了函数调用的开支;
第二处②是常规函数的调用进程,它是调用了test()办法,并且传入了一个Function目标,这个Function0便是咱们lambda表达式
第三处③是需求在编译器中才干看出作用,在编译器中,咱们能够看出teinlineTest()函数并没有调用者,而test()函数是在main()函数中有调用的当地。
这样咱们就能够直观的感触到,inline润饰的函数也便是内联函数在调用的时候并非直接调用此函数自身,而是将函数内的代码直接拷贝到调用途。这样带来优势便是:削减函数调用带来的开支,进步程序的功能;消除lambda表达式带来的额定开支,避免创建额定的目标。
noinline
上面咱们了解了inline内联函数的运用和优势,接着咱们看一下合作inline运用的noinline,看名字大致能够猜测到,noinline便是非内联的意思,也便是表明被noinline润饰的参数强制不允许内联,此参数作为一个普通的函数引用传递,并且noinline有必要搭配inline运用。下面仍是经过代码来直观感触下noinline的作用。
fun main() {
noinlineTest({
println("main inline")
}, {
println("main noInline")
})
}
// 界说一个内联函数,榜首个参数可内联运用,第二个参数运用noinline润饰,强制不内联
inline fun noinlineTest(block: () -> Unit, noinline no: () -> Unit) {
println("noinlineTest")
block()
no()
}
# log
noinlineTest
main inline
main noInline
noinlineTest({},{})函数为一个内联函数,两个lambda参数唯一不同的便是第二个参数被noinline润饰了,从log中能够看出,输出的信息在咱们意料之中,也并不能看出noinline带来的不同之处,咱们仍是得反编译看下生成的代码究竟变化了什么。
public final class NoinlineKt {
public static final void main() {
// ① no参数为一个Function,直接实例化了
Function0 no$iv = (Function0)null.INSTANCE;
// ② noinlineTest函数中打印的日志
System.out.println("noinlineTest");
// ③ block函数内代码直接拷贝到这
System.out.println("main inline");
// ④ 履行no参数详细的代码
no$iv.invoke();
}
public static final void noinlineTest(@NotNull Function0 block, @NotNull Function0 no) {
Intrinsics.checkNotNullParameter(block, "block");
Intrinsics.checkNotNullParameter(no, "no");
int $i$f$noinlineTest = false;
System.out.println("noinlineTest");
block.invoke();
no.invoke();
}
// $FF: synthetic method
public static void main(String[] args) {
main();
}
}
反编译的代码中咱们注释了四处当地,分别介绍了内联参数和noinline参数的履行过程:
榜首处①先实例化一个no参数,Kotlin的lambda在Java中对应的是Function目标;
第二处②直接拷贝内联函数noinlineTest()的println()办法输出日志;
第三处③直接拷贝内联参数的输出日志办法,从此处能够看到block参数被内联了,它被拷贝到调用途;
第四处④履行了noinline参数的内部代码
从上面反编译的代码咱们能够得出,noinline润饰的参数被强制非内联了,它仍是会去调用内部的代码,而非直接拷贝内部代码到调用途,这便是noinline关键字的作用。
crossinline
crossinline相对于前面inline和noinline来说,它运用的当地较少,个人的了解它的意思为强制内联的意思,它表明被润饰的lambda参数强制履行内联作用,一般咱们见到的运用它的当地都是在内联函数中运用了lambda表达式,并且在此表达式调用了内联函数的lambda参数,此刻假如不运用crossinline润饰参数,编译器会报错,下面咱们经过代码来说明
inline fun crossinlineTest(block: () -> Unit, crossinline cross: () -> Unit) {
println("crossinlineTest")
thread {
// 编译器会在此处报错:Can't inline 'block' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'block'
block()
cross()
}
}
上面便是一个内联函数运用crossinline的实例,咱们在内联函数中运用thread{}开启一个线程,并在新的线程中调用block()和cross()参数的履行,此刻编译器在block()调用途就直接报错,告知咱们block在此种场景下不能直接内联,它有可能包含了非本地的return,这样咱们就需求加上crossinline来润饰参数,从cross调用的状况就说明了它能够正常履行。
编译器为什么不允许咱们在thread{}中直接履行block()参数呢?
由于内联函数在调用的时候是直接将代码拷贝到调用途的,所以存在block()中直接return的状况,他会履行回来到调用途并且不再履行调用途后续的代码,看个详细代码了解一下:
inline fun inlineReturn(block: () -> Unit) {
block()
}
fun main() {
inlineReturn {
println("start")
return
// 此处将不会履行
println("end")
}
// 此处也不会履行
println("main")
}
# log
start
看上面代码,咱们直接在inlineReturn函数的block()中运用return来回来,成果它并非退出到内联函数,而是直接退出了main()函数,到这咱们记住inline函数是能够直接运用return来做出回来操作。
下面咱们再看看crossinline关键字的作用:
inline fun crossinlineReturn(crossinline block: () -> Unit) {
block()
}
fun main() {
crossinlineReturn {
println("start")
// 此处编译器会直接报错
return
println("end")
}
println("main")
}
crossinlineReturn和上面inlineReturn函数基本共同,仅仅block参数运用了crossinline润饰,此刻咱们假如还想运用return来操作回来,编译器会直接给出报错提示,告知咱们此刻不能够运用return,它需求指定回来的目的地,需求选用return@crossinlineReturn这样的形式,告知编译器仅仅退出到内联函数,并非直接退出main()函数,并且它的输出为:
# log
start
main
经过crossinline就能够禁止在内联的lambda表达式中运用return操作了。
好了,到这为止咱们现已将inline、noinline和crossinline三者的关系及其用法、作用都介绍完了,假如你有收获帮助点个关注吧,欢迎谈论区输出不同的看法和认为不正确的当地,ths!
