inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit)? = null) {
if (value != null) {
notNullBlock(value)
} else {
if(isNullBlock != null){
isNullBlock()
}
}
}
我试图编写一些高阶函数来促进开发,但这是错误的
答案 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, ...)
而是函数调用-内联将无济于事!
已添加功能-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);
}
}