为什么Kotlin内联函数参数不能为null

时间:2020-08-19 03:36:08

标签: kotlin lambda inline

enter image description here

inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit)? = null) {

if (value != null) {
    notNullBlock(value)
} else {
    if(isNullBlock != null){
        isNullBlock()
    }
}

}

我试图编写一些高阶函数来促进开发,但这是错误的

2 个答案:

答案 0 :(得分:5)

我认为这与inline functions和传递给它的lambda如何内联有关。内联修饰符会影响函数本身以及传递给它的lambda:所有这些都将内联到调用站点中。看来Kotlin不允许使用 nullable lambda。

如果您想要Ctrl+Shift+P参数的默认值,则可以使用大括号isNullBlock

isNullBlock: () -> Unit = {}

答案 1 :(得分:1)

Android开发者倡导者Florina Muntenescu提供了great post来说明inline的工作原理。按照所有说明,应该清楚为什么不允许可空的lambda。

简而言之:

由于使用了inline关键字,编译器将内联函数的内容复制到了调用站点,从而避免了创建新的Function对象。

这是内联关键字给我们带来的性能优势。但是,为了执行该操作,编译器必须确保始终传递lambda参数,无论该参数是否为空。当您尝试使lambda参数为可空时,编译器将无法将null lambda的内容复制到调用站点。同样,您无法执行!= null之类的比较操作,也无法使用?来包装应该内联的可选lambda,因为在编译时将不会存在lamda / function对象。下面有更多说明。

示例(详细说明)

在我的示例中,您的函数已更新,并将空lambda用作isNullBlock的默认参数:

inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit) = {}) {
    if (value != null) {
        notNullBlock(value)
    } else {
        isNullBlock()
    }
}

这是反编译为Java的isNullObject函数的非内联版本的用法。

科特琳码

class Test {
    init {
        isNullObject(null as? Int,
            {
                println("notNullBlock called")
                it
            },
            { println("isNullBlock called") })
        isNullObject(0, 
            {
                println("notNullBlock called")
                it
            },
            { println("isNullBlock called") })
    }
}

反编译的Java代码

public final class Test {
   public Test() {
      TestKt.isNullObject((Integer)null, (Function1)null.INSTANCE, (Function0)null.INSTANCE);
      TestKt.isNullObject(0, (Function1)null.INSTANCE, (Function0)null.INSTANCE);
   }
}

如您所见,没有什么异常的事情发生(尽管,很难理解null.INSTANCE是什么)。您的isNullObject函数以Kotlin中定义的三个参数传递。

这是您的内联函数如何使用相同的Kotlin代码进行反编译。


public final class Test {
   public Test() {
      Object value$iv = (Integer)null;
      int $i$f$isNullObject = false;
      int var3 = false;
      String var4 = "isNullBlock called";
      boolean var5 = false;
      System.out.println(var4);
      int value$iv = false;
      $i$f$isNullObject = false;
      int var8 = false;
      String var9 = "notNullBlock called";
      boolean var6 = false;
      System.out.println(var9);
   }
}

对于第一个函数调用,我们立即将if (value != null)语句解析为false,而传入的notNullBlock甚至没有最终代码。在运行时,无需每次都检查该值是否为null。由于isNullObject内联了Lambda,因此不会为Lambda参数生成Function对象。这意味着没有任何可检查的可为空性。另外,这就是为什么您无法保留对内联函数的lambda / function参数的引用的原因。

Object value$iv = (Integer)null;
int $i$f$isNullObject = false;
int var3 = false;
String var4 = "isNullBlock called";
boolean var5 = false;
System.out.println(var4);

但是内联仅在编译器能够在编译时获取给定参数的值时有效。如果第一个参数不是isNullObject(null as? Int, ...)isNullObject(0, ...)而是函数调用-内联将无济于事!

当编译器无法解析if语句时

已添加功能-getValue()。返回可选的Int。编译器无法提前知道getValue()调用的结果,因为它只能在运行时计算。因此,内联只做一件事-将isNullObject的全部内容复制到Test类构造函数中,并为每个函数调用两次。仍然有一个好处-我们摆脱了在运行时创建的4个Function实例来保存每个lambda参数的内容。

科特琳

class Test {
    init {
        isNullObject(getValue(),
            {
                println("notNullBlock called")
                it
            },
            { println("isNullBlock called") })
        isNullObject(getValue(),
            {
                println("notNullBlock called")
                it
            },
            { println("isNullBlock called") })
    }

    fun getValue(): Int? {
        if (System.currentTimeMillis() % 2 == 0L) {
            return 0
        } else {
            return null
        }
    }
}

反编译的Java

public Test() {
      Object value$iv = this.getValue();
      int $i$f$isNullObject = false;
      int it;
      boolean var4;
      String var5;
      boolean var6;
      boolean var7;
      String var8;
      boolean var9;
      if (value$iv != null) {
         it = ((Number)value$iv).intValue();
         var4 = false;
         var5 = "notNullBlock called";
         var6 = false;
         System.out.println(var5);
      } else {
         var7 = false;
         var8 = "isNullBlock called";
         var9 = false;
         System.out.println(var8);
      }

      value$iv = this.getValue();
      $i$f$isNullObject = false;
      if (value$iv != null) {
         it = ((Number)value$iv).intValue();
         var4 = false;
         var5 = "notNullBlock called";
         var6 = false;
         System.out.println(var5);
      } else {
         var7 = false;
         var8 = "isNullBlock called";
         var9 = false;
         System.out.println(var8);
      }

   }