欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

kotlin(七). 内联函数

程序员文章站 2022-04-15 18:43:55
...

一. 内联函数

使用高阶函数会带来一些运行时的效率损失:每个函数都是一个对象,并且会捕获一个闭包。即那些在函数体内会访问到的变量。

内存分配和虚拟调用(对于函数和类)会引入运行时间开销,但是通过内联化表达式可以消除这类的开销

为了解决这个问题,可以使用内联函数,用inline修饰的函数就是内联函数,inline修饰符影响函数本身和传给它的lambda表达式,所有这些都将内联到调用处,即编译器会把调用这个函数的地方,用这个函数的方法体进行替换,而不是创建一个函数对象并生成一个引用

kotlin代码

   test({i:Int,i1:Int->
           i*i1
       }) { i: Int, i1: Int ->
           i+i1
       }
    

    fun test(param:(Int,Int)->Int,param1:(Int,Int)->Int){
        println("test ${param(1,2)} ${param1(1,2)}")
    }

转换成java

 this.test((Function2)null.INSTANCE, (Function2)null.INSTANCE);


public final void test(@NotNull Function2 param, @NotNull Function2 param1) {
      Intrinsics.checkParameterIsNotNull(param, "param");
      Intrinsics.checkParameterIsNotNull(param1, "param1");
      String var3 = "test " + ((Number)param.invoke(1, 2)).intValue() + ' ' + ((Number)param1.invoke(1, 2)).intValue();
      boolean var4 = false;
      System.out.println(var3);
   }

可以看到编译器创建了两个 Lambda 的实例,并进行了两次函数调用。

那如果将calculate声明为内联函数呢:

protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300053);
      Map params = MapsKt.mapOf(new Pair[]{TuplesKt.to("type", "休闲"), TuplesKt.to("order", "1"), TuplesKt.to("page", "1")});
      HttpManager this_$iv = HttpManager.INSTANCE;
      int $i$f$test = false;
      HttpTaskDispatcher.Companion.addHttpTask((Function0)(new MainActivity$onCreate$$inlined$doPost$1(params)));
      $i$f$test = false;
      StringBuilder var10000 = (new StringBuilder()).append("test ");
      int i1 = 2;
      int i = 1;
      StringBuilder var8 = var10000;
      int var7 = false;
      int var9 = i * i1;
      var10000 = var8.append(var9).append(' ');
      i1 = 2;
      i = 1;
      var8 = var10000;
      var7 = false;
      var9 = i + i1;
      String var11 = var8.append(var9).toString();
      boolean var10 = false;
      System.out.println(var11);
   }

   public final void test(@NotNull Function2 param, @NotNull Function2 param1) {
      int $i$f$test = 0;
      Intrinsics.checkParameterIsNotNull(param, "param");
      Intrinsics.checkParameterIsNotNull(param1, "param1");
      String var4 = "test " + ((Number)param.invoke(1, 2)).intValue() + ' ' + ((Number)param1.invoke(1, 2)).intValue();
      boolean var5 = false;
      System.out.println(var4);
   }

即编译器会把调用这个函数的地方用这个函数的方法体进行替换,这样验证了之前的说法。

需要注意的是, 内联函数提高代码性能的同时也会导致代码量的增加,所以应避免内联函数过大。

二.禁用内联

如果一个内联函数可以接收多个 Lambda 表达式作为参数,默认这些 Lambda 表达式都会被内内联到调用处,如果需要某个 Lambda 表达式不被内联,可以使用noinline修饰对应的函数参数:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val params =
            mapOf(HttpKey.Key.TYPE to "休闲", HttpKey.Key.ORDER to "1", HttpKey.Key.PAGE to "1")

        HttpManager.doPost<ModelGirl>(params) {
            LogUtil.logD(it.showapi_res_body.pagebean.contentlist[0].cardUrl)
        }

       test({i:Int,i1:Int->
           i*i1

       }) { i: Int, i1: Int ->
           i+i1
       }
    }



    inline fun test(param:(Int,Int)->Int,noinline param1:(Int,Int)->Int){
        println("test ${param(1,2)} ${param1(1,2)}")
    }

转换成java后

 protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300053);
      Map params = MapsKt.mapOf(new Pair[]{TuplesKt.to("type", "休闲"), TuplesKt.to("order", "1"), TuplesKt.to("page", "1")});
      HttpManager this_$iv = HttpManager.INSTANCE;
      int $i$f$doPost = false;
      HttpTaskDispatcher.Companion.addHttpTask((Function0)(new MainActivity$onCreate$$inlined$doPost$1(params)));
      // 这里没有被内联
      Function2 param1$iv = (Function2)null.INSTANCE;
      int $i$f$test = false;
      StringBuilder var10000 = (new StringBuilder()).append("test ");
      int i1 = 2;
      int i = 1;
      StringBuilder var9 = var10000;
      int var8 = false;
      int var10 = i * i1;
      String var13 = var9.append(var10).append(' ').append(((Number)param1$iv.invoke(1, 2)).intValue()).toString();
      boolean var12 = false;
      System.out.println(var13);
   }

   public final void test(@NotNull Function2 param, @NotNull Function2 param1) {
      int $i$f$test = 0;
      Intrinsics.checkParameterIsNotNull(param, "param");
      Intrinsics.checkParameterIsNotNull(param1, "param1");
      String var4 = "test " + ((Number)param.invoke(1, 2)).intValue() + ' ' + ((Number)param1.invoke(1, 2)).intValue();
      boolean var5 = false;
      System.out.println(var4);
   }

一个内联函数没有可内联的函数参数并且没有具体化的类型参数,编译器会有警告,因为这样并不能带来什么好处,如果你不愿去掉内联修饰,可以使用@Suppress("NOTHING_TO_INLINE") 注解关闭这个警告。

三. 非局部返回

我们知道默认情况下,在高阶函数中,要显式的退出(返回)一个 Lambda 表达式,需要使用 [email protected]标签的语法,不能使用裸return,但这样也不能使高阶函数和包含高阶函数的函数退出。例如:

fun message(block: () -> Unit) {
    block()
    println("-----")
}

fun test() {
    message {
        println("Hello")
        return@message
    }
    println("World")
}
fun main(args: Array<String>) {
    test()
}
// 输出
Hello
-----
World

但如果把 Lambda 表达式作为参数传递给一个内联函数,就可以在 Lambda 表达式中正常的使用return语句了,并且会使该内联函数和包含该内联函数的函数退出(返回),这种操作就是非局部返回。例如:

inline fun message(block: () -> Unit) {
    block()
    println("-----")
}

fun test() {
    message {
        println("Hello")
        return
    }
    println("World")
}
fun main(args: Array<String>) {
    test()
}
// 输出
Hello

注意,由于非局部返回的原因,这里只输出了Hello

在使用了非局部返回后,Lambda 表达式中return的返回值受调用该内联函数的函数的返回值类型影响。例如:

fun test(): Boolean {
    message {
        println("Hello")
        return false
    }
    println("World")
    return true
}

四、禁用非局部返回(crossinline)

从前边已经知道,通过内联函数可以使 Lambda表达式实现非局部返回,但是,如果一个内联函数的函数类型参数被crossinline修饰,则对应传入的 Lambda表达式将不能非局部返回了,只能局部返回了。还是用之前的例子修改:

inline fun message(crossinline block: () -> Unit) {
    block()
    println("-----")
}

fun test() {
    message {
        println("Hello")
        return@message
    }
    println("World")
}
fun main(args: Array<String>) {
    test()
}
// 输出
Hello
-----
World

通过crossinline可以禁用掉非局部返回,但有什么意义呢?这其实是有实际的场景需求的,看个例子:

interface Calculator {
    fun calculate(a: Int, b: Int): Int
}

inline fun test(block: (Int, Int) -> Int) {
    val c = object : Calculator {
        override fun calculate(a: Int, b: Int): Int = block(a, b)
    }
    c.calculate(3, 7)
}

首先定义一个Calculator计算接口,然后在内联函数test中创建Calculator的一个对象表达式,重写calculate方法时,我们让calculate的函数体是test函数的block参数,当block是 Lambda表达式时,由于非局部返回的原因,导致calculate函数的返回值不是预期的,进而发生异常,为了避免这种情况的发生,所以就有必要使用crossinline来禁用非局部返回,来保证calculate的返回值类型是安全的。

上边的代码会有这样一个错误提示:

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

使用crossinline后就正常了:

inline fun test(crossinline block: (Int, Int) -> Int) {
    val c = object : Calculator {
        override fun calculate(a: Int, b: Int): Int = block(a, b)
    }
    c.calculate(3, 7)
}

五、具体化的类型参数(reified)

对于一个泛型函数,如果需要访问泛型参数的类型,但由于泛型类型被擦除的原因,可能无法直接访问,但通过反射还是可以做到的,例如:

fun <T> test(param: Any, clazz: Class<T>) {
    if (clazz.isInstance(param)) {
        println("参数类型匹配")
    } else {
        println("参数类型不匹配")
    }
}
fun main(args: Array<String>) {
    test("Hello World", String::class.java)
    test(666, String::class.java)
}
// 输出
参数类型匹配
参数类型不匹配

功能虽然实现了,但是不够优雅,Kotlin 中有更好的办法来实现这样的功能。

在内联函数中支持具体化的参数类型,即用reified来修饰需要具体化的参数类型,这样我们用reified来修饰泛型的参数类型,以达到我们的目的:

inline fun <reified T> test(param: Any) {
    if (param is T) {
        println("参数类型匹配")
    } else {
        println("参数类型不匹配")
    }
}

调用的过程也变得简单了:

fun main(args: Array<String>) {
    test<String>("Hello World")
    test<String>(666)
}
相关标签: 安卓 安卓开发