为什么 Kotlin 内联函数的参数必须为非空?

4

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()
    }
}

我尝试编写一些高阶函数来方便开发,但出现了错误。


顺便提一下,你的代码中不需要类型 R - Lino
为什么这么说?R是结果,不是吗? - Sean Eli
1
但是结果从未在您的代码中使用。 - Lino
2个回答


3

有一篇来自Android Developer Advocate Florina Muntenescu的很棒的文章介绍了inline是如何工作的。通过解释,应该很清楚为什么不允许Nullable Lambda了。

简而言之:

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

这就是inline关键字带来的性能优势。但为了实现这个,编译器必须确保您始终传递lambda参数,无论它是否为空。当您尝试使lambda参数可空时,编译器将无法将null lambda的内容复制到调用点。同样,您也不能执行比较操作,例如!= null,或者使用?来取消包装应该内联的可选lambda 因为在编译时不会产生lambda/function对象。以下是更多解释。

示例(长说明)

在我的示例中,更新了您的函数,并将空lambda作为默认参数传递给isNullBlock

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

下面是将您的isNullObject函数的非内联版本反编译为Java的使用方法。

Kotlin 代码:

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 是什么)。这是以 Kotlin 定义的方式传递了三个参数的 isNullObject 函数。

下面是使用相同的 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甚至没有最终被编译进代码。运行时,将不再需要每次检查值是否为空。因为isNullObject与其lambda内联,所以没有为lambda参数生成Function对象。这意味着没有东西可供空值检查。这也是为什么您不能持有内联函数的lambda/函数参数的引用的原因。

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参数的内容。

Kotlin

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);
      }

   }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接