Java转Kotlin:内联函数

1,700 阅读4分钟

1 内联函数

1.1 什么是内联函数

看以下forEach()高阶函数的调用:

//Kotlin
//CodeSegment1
var ints = intArrayOf(1, 2, 3, 4)

fun main() {
    ints.forEach {
        println(it)
    }
}

main()函数内调用了intsforEach()高阶函数,正常一个函数的调用过程是一个压栈、调用执行、出栈的过程,如果被调用函数执行内容过于简单,例如这里的println(it),相对于调用执行的开销,压栈和出栈的开销将会过大。为了解决这个问题,内联函数的概念出现了。

内联函数在Kotlin中用inline关键字标识,在内联函数被调用的时候,编译器直接将直接把函数的执行内容“插入”到调用函数的位置,以避免压栈和出栈的开销。

我们看一下IntArrayforEach()的定义:

//Kotlin
public inline fun kotlin.IntArray.forEach(action: (kotlin.Int) -> kotlin.Unit): kotlin.Unit { /* compiled code */ }

因为有inline关键字,所以编译器在执行CodeSegment1时,完全等价于以下代码:

//Kotlin
//CodeSegment2
var ints = intArrayOf(1,2,3,4)

fun main() {
    for (i in ints) {
        println(i)
    }
}

避免了调用函数带来的压栈、出栈开销。

定义函数时,在前面添加一个inline关键字,该函数就被定义成内联函数。

注意!并非所有函数都适合定义成内联函数,需要看函数执行的开销,往往函数执行开销越小,调用函数的压栈和出栈开销越“不划算”,越应该定义成内联函数。

1.2 内联函数与高阶函数更配

高阶函数要么传入参数包含函数类型,要么输出结果是函数类型,所以高阶函数直接调用产生的压栈、出栈开销将比较大,所以一般都会定义成内联函数。

定义一个计算代码块执行耗时的高阶函数cost(block: ()->Unit)

//Kotlin
inline fun cost(block: () -> Unit) {
    val start: Long = System.currentTimeMillis()
    block()
    println("${System.currentTimeMillis() - start} ms")
}

当我们在main()函数中调用cost()函数时:

//Kotlin
fun main() {
    cost { println("Hello inline function") }
}

编译器实际执行的代码相当于:

//Kotlin
fun main() {
    val start: Long = System.currentTimeMillis()
    println("Hello inline function")
    println("${System.currentTimeMillis() - start} ms")
}

这里发生了两次内联

  1. 函数本身被内联到调用处;
  2. 函数的函数参数被内联到调用处。

1.3 内联函数的return

1.3.1 local return

设想这样一个场景,现在需要打印CodeSegment1中的ints,但是,当遇到3时,不打印,如何在forEach()中实现这一点呢?操作如下:

//Kotlin
var ints = intArrayOf(1, 2, 3, 4)

fun main() {
    ints.forEach {
        if (it == 3) return@forEach
        println(it)
    }
    println("Dividing")
    ints.forEach {
        if (it == 3) return
        println(it)
    }
    println("Ending")
}

控制台打印:

return@forEachreturn的打印结果完全不同。

  • return@forEach将提前结束本次循环,相当于continue
  • return将结束所在函数,此处即为main()函数,因为Ending没有打印。

return@forEach这样的返回称为local return

1.3.2 non-local return

上面的return就是non-local return

看这样一个例子,如果将高阶函数定义成内联函数

//Kotlin
inline fun nonLocalReturn(block: ()->Unit){
    block()
}

fun main() {
    println("Starting")
    nonLocalReturn { return }
    println("Ending")
}

控制台将打印:

如果将高阶函数定义成非内联函数

则只能执行local return。

对于内联函数,因为没有压栈和出栈操作,所以直接return对内联函数无效,效果将出现在包裹内联函数的函数,这里是main()函数。

看这样一个例子:

//Kotlin
inline fun func1(block:()->Unit){
    println("func1 starting")
    block()
    println("func1 ending")
}

inline fun func2(){
    println("func2 starting")
    return
    println("func2 ending")
}

fun main() {
    func1 {
        func2()
    }
}

Qfunc1 ending会不会打印?

A:会打印:

按理说,func2()也没有压栈操作,return操作对其应该无效,同样的,包裹func2()func1()也没有压栈操作,returnfunc1()也无效,这个return应该直接使得main()函数弹出栈,不打印func1 ending才对?

字节码反编译如下:

//Java
package imooc.chapter_6.inline;

import kotlin.Metadata;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\u001a\u0017\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00010\u0003H\u0086\b\u001a\t\u0010\u0004\u001a\u00020\u0001H\u0086\b\u001a\u0006\u0010\u0005\u001a\u00020\u0001¨\u0006\u0006"},
   d2 = {"func1", "", "block", "Lkotlin/Function0;", "func2", "main", "LearnKotlin.main"}
)
public final class Inline_funcKt {
   public static final void func1(@NotNull Function0 block) {
      int $i$f$func1 = 0;
      Intrinsics.checkParameterIsNotNull(block, "block");
      String var2 = "func1 starting";
      boolean var3 = false;
      System.out.println(var2);
      block.invoke();
      var2 = "func1 ending";
      var3 = false;
      System.out.println(var2);
   }

   public static final void func2() {
      int $i$f$func2 = 0;
      String var1 = "func2 starting";
      boolean var2 = false;
      System.out.println(var1);
   }

   public static final void main() {
      int $i$f$func1 = false;
      String var1 = "func1 starting";
      boolean var2 = false;
      System.out.println(var1);
      int var3 = false;
      int $i$f$func2 = false;
      String var5 = "func2 starting";
      boolean var6 = false;
      System.out.println(var5);
      var1 = "func1 ending";
      var2 = false;
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

怎么解释?

1.3.3 crossinline

添加crossinline关键字,禁止non-local return

1.3.4 noinline

添加noinline关键字禁止函数参数内联:

这样的情况下,高阶函数前面的inline关键字将是多余的:

2 内联属性

没有back-field的属性的getter/setter可以设置内联;

3 内联函数的限制

  • public/protected的内联函数只能访问对应类的public成员;
  • 内联函数的内联函数参数不能被存储(不能赋值给变量);
  • 内联函数的内联函数参数只能被传递给其他的内联函数。

小结