转载

Java转Kotlin:内联函数

看以下 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 关键字,该函数就被定义成内联函数。

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

Java转Kotlin:内联函数

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")
}
复制代码

控制台打印:

Java转Kotlin:内联函数

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")
}
复制代码

控制台将打印:

Java转Kotlin:内联函数

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

Java转Kotlin:内联函数

则只能执行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()
    }
}
复制代码

Q: func1 ending 会不会打印?

A:会打印:

Java转Kotlin:内联函数

按理说, 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

Java转Kotlin:内联函数

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

Java转Kotlin:内联函数

1.3.4 noinline

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

Java转Kotlin:内联函数

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

Java转Kotlin:内联函数

2 内联属性

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

Java转Kotlin:内联函数

3 内联函数的限制

  • public/protected 的内联函数只能访问对应类的 public 成员;
  • 内联函数的内联函数参数不能被存储(不能赋值给变量);
  • 内联函数的内联函数参数只能被传递给其他的内联函数。
原文  https://juejin.im/post/5ef16e4d6fb9a05866675f20
正文到此结束
Loading...