kotlin(七). 内联函数

一. 内联函数

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

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

为了解决这个问题,可以使用内联函数,用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 表达式,需要使用 return@标签的语法,不能使用裸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)
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值