kotlin 内联函数 Inline functions

196 阅读3分钟

kotlin 内联函数 Inline functions

Kotlin 内联函数 inline - 简书 (jianshu.com)

1.inline functions的作用

使用高阶函数会带来一定的运行时损失:每个函数都是一个对象,它捕获一个闭包。闭包是可以在函数体中访问的变量的范围。内存分配(对于函数对象和类)和虚拟调用引入了运行时开销。

但在许多情况下,这种开销似乎可以通过内联lambda表达式来消除。下面显示的函数是这种情况的很好的例子。

使用inline

private fun ordinaryFunction(block: () -> Unit) {
    println("ordinaryFunction")
    block.invoke()
}

private fun foo() {
    ordinaryFunction {
        println("ordinaryFunction lambda invoke")
    }
}

fun main() {
    println("hello world")
    println( measureTimeMillis {
        repeat((0..100000).count()) {
            foo()
        }
    })
}

生成的代码

public final class TestNotInlineKt {
   private static final void ordinaryFunction(Function0 block) {
      String var1 = "ordinaryFunction";
      System.out.println(var1);
      block.invoke();
   }

   private static final void foo() {
      ordinaryFunction((Function0)null.INSTANCE);
   }

   public static final void main() {
      String var0 = "hello world";
      System.out.println(var0);
      int $i$f$measureTimeMillis = false;
      long start$iv = System.currentTimeMillis();
      int var3 = false;
      int var4 = 0;
      var4 = CollectionsKt.count((Iterable)(new IntRange(var4, 100000)));
      int var5 = 0;

      while(var5 < var4) {
         ++var5;
         int var8 = false;
         foo();
      }

      long var10 = System.currentTimeMillis() - start$iv;
      System.out.println(var10);
   }

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

fun ordinaryFunction使用inline修饰后,

-private fun ordinaryFunction(block: () -> Unit) {...}
+private inline fun ordinaryFunction(block: () -> Unit) {}

继续看看生成的java代码变成如下

public final class TestNotInlineKt {
   private static final void ordinaryFunction(Function0 block) {
      int $i$f$ordinaryFunction = 0;
      String var2 = "ordinaryFunction";
      System.out.println(var2);
      block.invoke();
   }

   private static final void foo() {
      int $i$f$ordinaryFunction = false;
      String var1 = "ordinaryFunction";
      System.out.println(var1);
      int var2 = false;
      String var3 = "ordinaryFunction lambda invoke";
      System.out.println(var3);
   }

   public static final void main() {
      String var0 = "hello world";
      System.out.println(var0);
      int $i$f$measureTimeMillis = false;
      long start$iv = System.currentTimeMillis();
      int var3 = false;
      int var4 = 0;
      var4 = CollectionsKt.count((Iterable)(new IntRange(var4, 100000)));
      int var5 = 0;

      while(var5 < var4) {
         ++var5;
         int var8 = false;
         foo();
      }

      long var10 = System.currentTimeMillis() - start$iv;
      System.out.println(var10);
   }

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

经对比,最大的变化是

public final class TestNotInlineKt {
   private static final void ordinaryFunction(Function0 block) {
      String var1 = "ordinaryFunction";
      System.out.println(var1);
      block.invoke();
   }

   private static final void foo() {
      ordinaryFunction((Function0)null.INSTANCE);
   }
}

变成了如下。

private static final void ordinaryFunction(Function0 block) {
      int $i$f$ordinaryFunction = 0;
      String var2 = "ordinaryFunction";
      System.out.println(var2);
      block.invoke();
}

private static final void foo() {
	int $i$f$ordinaryFunction = false;
	String var1 = "ordinaryFunction";
	System.out.println(var1);
	int var2 = false;
	String var3 = "ordinaryFunction lambda invoke";
	System.out.println(var3);
}

可以看出,调用foo()方法时,从方法调用方法并且传递对象变成了把原来ordinaryFunction里的方法内联/平铺到了foo()方法内。

可以看出以下几点

1:inline是编译期把代码优化

2.减少了内存嵌套调用,使得方法的调用栈少一层,

2.什么情况下需要使用inline

对一个普通方法(非高阶函数)使用inline

private inline fun noParamsFun() {

}

可以看到编译器看到警告

Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types

翻译过来就是

内联带来的预期性能影响是微不足道的。内联最适合参数为函数类型的函数

3.noinline

如果你不希望所有传递给内联函数的lambda都内联,用noinline修饰符标记一些函数参数:

内联有这么多好处,为什么有时又要标明非内联呢

假设有一个高阶函数,有两个参数都是函数类型。


private fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
    println("ordinaryFunction")
    block.invoke()
    block2.invoke(System.currentTimeMillis())
}

private fun foo() {
    ordinaryFunction(block = {
        println("ordinaryFunction lambda block1 invoke")
    }, block2 = { it ->
        println("ordinaryFunction lambda block2 invoke $it")
    })
}

fun main() {
    println("hello world")
    println(measureTimeMillis {
        repeat((0..100000).count()) {
            foo()
        }
    })
}

愉快的编译成功并运行,

加上inline后,也是愉快的编译并运行

-private fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
+private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
    println("ordinaryFunction")
    block.invoke()
    block2.invoke(System.currentTimeMillis())
}

貌似加不加inline都可以正常运行

但假设作为参数的函数,并不是只即执行,而是作为参数继续传递呢?

private fun testHigherOrderFunction( block2: (Long) -> Unit) {
    block2.invoke(System.currentTimeMillis())
}

private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
    println("ordinaryFunction")
    block.invoke()
    //inline修饰的方法里的函数参数作为参数继续传递
    testHigherOrderFunction(block2)
}

可以看到会编译失败,会提示

Add 'noinline' modifier to the parameter declaration

在参数声明中添加'noinline'修饰符

-private inline fun ordinaryFunction(block: () -> Unit, block2: (Long) -> Unit) {
+private inline fun ordinaryFunction(block: () -> Unit, noinline block2: (Long) -> Unit) {
    println("ordinaryFunction")
    block.invoke()
    testHigherOrderFunction(block2)
}

可以看到生成的java代码出是没有内联block2函数的,只内联了block1函数。

private static final void foo() {
    Function1 block2$iv = (Function1)null.INSTANCE;
    int $i$f$ordinaryFunction = false;
    String var2 = "ordinaryFunction";
    System.out.println(var2);
    int var3 = false;
    String var4 = "ordinaryFunction lambda block1 invoke";
    System.out.println(var4);
    access$testHigherOrderFunction(block2$iv);
}

内联lambdas只能在内联函数内部调用或作为内联参数传递。然而,可以以您喜欢的任何方式操作非内联lambda,包括存储在字段中或传递。

inline fun 是内联所有的函数参数的,可以指定哪些需要内联,哪些不需要

4.inline与Non-local returns

在Kotlin中,只能使用普通的、非限定的返回来退出命名函数或匿名函数。要退出lambda,请使用标签。禁止在lambda表达式中使用裸返回,因为lambda表达式不能使外围函数返回:

private  fun ordinaryFunction(block: () -> Unit) {
    println("hi!")
    block.invoke()
}

private fun foo() {
    ordinaryFunction {
        println("test return")
        //ERROR: cannot make `foo` return here
       // return //lambda不支持这样直接返回,需要使用标签
        return@ordinaryFunction
    }
    //普通函数return
    return
}

fun main() {
    foo()
}

这样的返回(位于lambda中,但退出封闭函数)称为non-local returns.非本地返回。这类构造通常发生在循环中,内联函数通常包含:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // returns from hasZeros
    }
    return false
}

fun main() {
    println("test")
    println(hasZeros(listOf(0,1,2,3,4,5,6,7,8,9)))
    println(hasZeros(listOf(1,2,3,4,5,6,7,8,9)))
}

上面的代码之所以能在forEachreturnhasZeros的调用处,是因为forEach本身就是一个inline fun

/**
 * Performs the given [action] on each element.
 */
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

看生成的java代码就一目了然了。

public final class TestNotUseInline2Kt {
   public static final boolean hasZeros(@NotNull List ints) {
      Intrinsics.checkNotNullParameter(ints, "ints");
      Iterable $this$forEach$iv = (Iterable)ints;
      int $i$f$forEach = false;
      Iterator var3 = $this$forEach$iv.iterator();

      int it;
      do {
         if (!var3.hasNext()) {
            return false;
         }

         Object element$iv = var3.next();
         it = ((Number)element$iv).intValue();
         int var6 = false;
      } while(it != 0);

      return true;
   }

   public static final void main() {
      String var0 = "test";
      System.out.println(var0);
      boolean var1 = hasZeros(CollectionsKt.listOf(new Integer[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
      System.out.println(var1);
      var1 = hasZeros(CollectionsKt.listOf(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9}));
      System.out.println(var1);
   }

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

kotlin里常见的inline函数 作用域函数。with/let/run/apply


/**
 * Calls the specified function [block] and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

5.crossinline交叉内联

假设有以下函数

fun f(body: () -> Unit) {
    val runnable = Runnable { body() }
    runnable.run()
}

fun main() {
    f {
        println("running")
    }
}

愉快的编译并运行

加上inline修改后

-fun f(body: () -> Unit) {}
+inline fun f(body: () -> Unit) {}

编译失败,查看提示

Can't inline 'body' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'body'

不能在这里内联'body':它可能包含non-local returns非本地返回。为参数声明body添加'crossinline'修饰符

当然,解决方法是可以组body加上noinline声明即可编译通过,但这一个方法只有一个参数,noinline和inline一起使用,相当于inline多此一举。

-inline fun f(body: () -> Unit) {}
+inline fun f(noinline body: () -> Unit) {}

按提示加上crossinline修饰。

inline fun f(crossinline body: () -> Unit) {
    val runnable = Runnable { body() }
    runnable.run()
}

fun main() {
    f {
        println("running")
        //return //直接return是不允许的。
        return@f
    }
}

看看生成的代码

public final class TestCrossinlineKt {
   public static final void f(@NotNull final Function0 body) {
      int $i$f$f = 0;
      Intrinsics.checkNotNullParameter(body, "body");
      Runnable runnable = (Runnable)(new Runnable() {
         public final void run() {
            body.invoke();
         }
      });
      runnable.run();
   }

   public static final void main() {
      int $i$f$f = false;
      Runnable runnable$iv = (Runnable)(new TestCrossinlineKt$main$$inlined$f$1());
      runnable$iv.run();
   }

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

import kotlin.Metadata;
public final class TestCrossinlineKt$main$$inlined$f$1 implements Runnable {
   public final void run() {
      int var1 = false;
      String var2 = "running";
      System.out.println(var2);
   }
}

对比不加inlin的代码

原kotlin代码

fun f(body: () -> Unit) {
    val runnable = Runnable { body() }
    runnable.run()
}

fun main() {
    f {
        println("running")
        return@f
    }
}

生成的代码


public final class TestCrossinlineKt {
   public static final void f(@NotNull final Function0 body) {
      Intrinsics.checkNotNullParameter(body, "body");
      Runnable runnable = (Runnable)(new Runnable() {
         public final void run() {
            body.invoke();
         }
      });
      runnable.run();
   }

   public static final void main() {
      f((Function0)null.INSTANCE);
   }

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

6.具体化的类型参数 Reified type parameters

假设有一个这样的方法,用于类型转换

fun <T> Any.caseTo(): T? {
    return this as? T
}

fun main() {
    val r1 = "1".caseTo<String>()
    val r2 = runCatching {
        //这里会抛异常
        "1".caseTo<Int>()
    }
    println("r1:$r1\tr2:$r2")
}

为了避免异常,一般会有以下方案

显式传递class信息

fun <T> Any.caseTo(clazz: Class<T>): T? {
    return if(clazz.isInstance(this)) this as T else null
}


fun main() {
    val r1 = "1".caseTo(String::class.java)
    val r2 = runCatching {
        //这里不再上抛异常,但返回null
        1.caseTo(Int::class.java)
    }
    println("r1:$r1\tr2:$r2")
}

但有没有更加优雅的调用方式呢

例如

-"1".caseTo(String::class.java)
+"1".caseTo<String>()

为了实现这一个目的,kotlin提供的方法是:inline + reified(reified关键字必须和inline关键字一起使用)

把代码稍稍修改一下

inline fun <reified T> Any.caseTo(): T? {
    return if(this is T) this  as T else null
}

fun main() {
    val r1 = "1".caseTo<String>()
    val r2 = runCatching {
        //这里不再上抛异常 并且正常返回值
        1.caseTo<Int>()
    }
    println("r1:$r1\tr2:$r2")
}

reified与反射

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

7.Inline properties

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }
inline var bar: Bar
    get() = ...
    set(v) { ... }

8.关于内联的其他一些事情

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // non-local return directly to the caller of foo()
        print(it)
    }
    //因为上面的forEach是内联的,所以会直接结束foo()方法,而不是结束forEach,所以这下面这一句无法执行到的。
    println("this point is unreachable")
}

fun main() {
    foo()
}

声明返回到哪里

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // local return to the caller of the lambda - the forEach loop
        print(it)
    }
    print(" done with implicit label")
}