inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit)? = null) {
if (value != null) {
notNullBlock(value)
} else {
if(isNullBlock != null){
isNullBlock()
}
}
我尝试编写一些高阶函数来方便开发,但出现了错误。
我认为这与如何内联传递给它的 inline
函数和 lambda 表达式有关。 inline 修饰符会影响函数本身和传递给它的 lambda 表达式:所有这些都将被内联到调用点。 Kotlin 似乎不允许使用可空(nullable)lambda表达式。
如果你想要为 isNullBlock
参数提供一些默认值,可以使用空括号isNullBlock: () -> Unit = {}
:
inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: () -> Unit = {}) {
if (value != null) {
notNullBlock(value)
} else {
isNullBlock()
}
}
有一篇来自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, ...)
,而是一个函数调用,则内联将没有任何好处!
添加了一个函数 - 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);
}
}
R
。 - Lino