kotlin - 内联函数
首先我们了解一下什么是高阶函数:高阶函数就是为函数传入函数或者lambda表达式的函数。由于在我们执行传进来的函数或lambda表达式时都会为它们创建一个对象,在函数压找出栈的过程也是比较消耗性能的。所以为了避免这种函数的调用过程,编译器就会复制被调用的函数到原来的执行代码中。为了让告知编译器帮我们复制、粘贴哪些代码可以通过内联函数来实现。
内联函数的使用
只要用inline
关键字修饰函数,那么这个函数就变成了内联函数。代码如下:
inline fun calculate(a: Int, b: Int, cal: (Int, Int) -> String) {
println(cal(a, b))
}
fun main(args: Array<String>) {
calculate(3, 4) { a, b ->
"$a+$b=${a + b}"
}
}
不加inline关键字反编译后对应的java代码如下:
public final class InlineFunDemoKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
calculate(3, 4, (Function2)null.INSTANCE);
}
public static final void calculate(int a, int b, @NotNull Function2 cal) {
Intrinsics.checkParameterIsNotNull(cal, "cal");
Object var3 = cal.invoke(a, b);
boolean var4 = false;
System.out.println(var3);
}
}
加了inline关键字反编译后对应的java代码如下:
public final class InlineFunDemoKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
byte a$iv = 3;
int b$iv = 4;
int $i$f$calculate = false;
int var6 = false;
String var5 = "" + a$iv + '+' + b$iv + '=' + (a$iv + b$iv);
boolean var4 = false;
System.out.println(var5);
}
public static final void calculate(int a, int b, @NotNull Function2 cal) {
int $i$f$calculate = 0;
Intrinsics.checkParameterIsNotNull(cal, "cal");
Object var4 = cal.invoke(a, b);
boolean var5 = false;
System.out.println(var4);
}
}
从上面这些代码我们很容看出来加了inline
和不加inline
关键字的区别,它会把inline
函数的执行体拷贝到调用处。
部分禁止内联
在使用inline关键字后,这个函数和传入的函数或者lambda表达式都会被内联化,如果我们希望部分函数或lambda表达式不被内联化,可以使用noinline关键字来修饰该函数或者表达式。
例如如下代码:
fun main(args: Array<String>) {
calculate(3, 4,{ println("开始计算")}) { a, b ->
"$a+$b=${a + b}"
}
}
inline fun calculate(a: Int, b: Int, noinline begin: () -> Unit, cal: (Int, Int) -> String) {
begin()
println(cal(a, b))
}
反编译后对应的java代码:
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
byte a$iv = 3;
byte b$iv = 4;
Function0 begin$iv = (Function0)null.INSTANCE;
int $i$f$calculate = false;
begin$iv.invoke();
int var7 = false;
String var6 = "" + a$iv + '+' + b$iv + '=' + (a$iv + b$iv);
boolean var5 = false;
System.out.println(var6);
}
public static final void calculate(int a, int b, @NotNull Function0 begin, @NotNull Function2 cal) {
int $i$f$calculate = 0;
Intrinsics.checkParameterIsNotNull(begin, "begin");
Intrinsics.checkParameterIsNotNull(cal, "cal");
begin.invoke();
Object var5 = cal.invoke(a, b);·
boolean var6 = false;
System.out.println(var5);
}
从上面的代码可以看出begin函数确实没有被内联化
注意:一个内联函数如果没有函数或者lambda表达式作为参数时,那么这个内联函数就没有多大意义,编译器会有警告信息。
什么情况下使用内联函数
函数或者lambda表达式的执行代码量不大时候就应该使用内联函数
如果函数或者lambda表达式代码量很大而且在多个地方调用那么就不适合用内联函数,因为有可能大量的复制代码比方法调用更影响性能。
非局部返回
- 前面也提到默认情况下lambda表达式不允许直接写return的,这是因为如果是非内联lambda表达式,会在使用的时候额外的生成一个函数对象,所以这种表达式不可能用于返回它所在的函数。默认lambda表达式最后一条语句就是他的返回值。
- 内联的lambda表达式的本质是代码的拷贝,所以它的内部可以写return返回的是它所在的函数。
实例代码如下:
inline fun each(data:Array<Int>,fn:(Int)->Unit){
for (el in data){
fn(el)
}
}
fun main(args: Array<String>) {
val array = arrayOf(1, 2, 3)
each(array){
println(it)
return //如each函数有inline修饰时,return返回的是main函数
// 如果没有没有inline修饰编译会报'return' is not allowed here错误
}
}
如果硬要在没有inline修饰的lambda表达始终写return,只能通过 return@方法名
的语法不能单独使用return。
什么是非局部返回
就是在inline修饰的lambda中使用return返回的是它所在的函数而不是lambda函数本身的操作。
禁用非局部返回
通过内联函数可以使 Lambda表达式实现非局部返回,但是,如果一个内联函数的函数类型参数被crossinline
修饰,则对应传入的 Lambda表达式将不能非局部返回了,只能局部返回了。还是用之前的例子修改:
inline fun each(data:Array<Int>,crossinline fn:(Int)->Unit){
for (el in data){
fn(el)
}
}
fun main(args: Array<String>) {
val array = arrayOf(1, 2, 3)
each(array){
println(it)
return@each //如each函数有inline修饰时,return返回的是main函数
// 如果没有没有inline修饰编译会报'return' is not allowed here错误
}
}
上面的代码在函数类型参数上加了crossinline
关键字,这是我们在传入的lambda表达式中就不能写return来非局部返回了,只能写return@each
使其变为局部返回。
适用的场景
如果传入的lambda表达式不是直接在函数中调用的,而是在函数内部的局部函数或者匿名对象中调用的,而这个函数的后面还有别的逻辑这时候我们就要禁用非局部返回,否则这个函数在传入的函数执行之后的逻辑都无法执行了。