为什么 Kotlin 内联函数参数不能为空
why Kotlin inline function params is must not be null
inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit)? = null) {
if (value != null) {
notNullBlock(value)
} else {
if(isNullBlock != null){
isNullBlock()
}
}
}
我试着写了一些高阶函数方便开发,但是报错
我认为这与 inline
functions 和传递给它的 lambda 的内联方式有关。 inline 修饰符影响函数本身和传递给它的 lambda:所有这些都将内联到调用站点。似乎 Kotlin 不允许使用 nullable lambdas.
如果您想要 isNullBlock
参数的一些默认值,您可以使用空括号 isNullBlock: () -> Unit = {}
:
inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: () -> Unit = {}) {
if (value != null) {
notNullBlock(value)
} else {
isNullBlock()
}
}
great post 解释了 inline
的工作原理 Android Developer Advocate Florina Muntenescu。在所有解释之后,应该清楚为什么不允许为空的 lambda。
简而言之:
Because of the inline
keyword, the compiler copies the content of the inline function to the call site, avoiding creating a new Function
object.
这就是内联关键字给我们带来的性能优势。但是为了做到这一点,编译器必须确保您始终传入一个 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()
}
}
这里是 非内联 版本的 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
是什么)。您的 isNullObject
函数使用 Kotlin 中定义的三个参数调用。
以下是您的 inlined 函数将如何使用相同的 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()
。 Returns 可选 Int
。编译器无法提前知道 getValue()
调用的结果,因为它只能在运行时计算。因此,内联只做一件事——将 isNullObject
的全部内容复制到 Test
class 构造函数中,并为每个函数调用执行两次。还有一个好处 - 我们摆脱了在运行时创建的 4 Function
s 个实例来保存每个 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);
}
}
inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: (() -> Unit)? = null) {
if (value != null) {
notNullBlock(value)
} else {
if(isNullBlock != null){
isNullBlock()
}
}
}
我试着写了一些高阶函数方便开发,但是报错
我认为这与 inline
functions 和传递给它的 lambda 的内联方式有关。 inline 修饰符影响函数本身和传递给它的 lambda:所有这些都将内联到调用站点。似乎 Kotlin 不允许使用 nullable lambdas.
如果您想要 isNullBlock
参数的一些默认值,您可以使用空括号 isNullBlock: () -> Unit = {}
:
inline fun <T, R> isNullObject(value: T?, notNullBlock: (T) -> R, isNullBlock: () -> Unit = {}) {
if (value != null) {
notNullBlock(value)
} else {
isNullBlock()
}
}
great post 解释了 inline
的工作原理 Android Developer Advocate Florina Muntenescu。在所有解释之后,应该清楚为什么不允许为空的 lambda。
简而言之:
Because of the
inline
keyword, the compiler copies the content of the inline function to the call site, avoiding creating a newFunction
object.
这就是内联关键字给我们带来的性能优势。但是为了做到这一点,编译器必须确保您始终传入一个 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()
}
}
这里是 非内联 版本的 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
是什么)。您的 isNullObject
函数使用 Kotlin 中定义的三个参数调用。
以下是您的 inlined 函数将如何使用相同的 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()
。 Returns 可选 Int
。编译器无法提前知道 getValue()
调用的结果,因为它只能在运行时计算。因此,内联只做一件事——将 isNullObject
的全部内容复制到 Test
class 构造函数中,并为每个函数调用执行两次。还有一个好处 - 我们摆脱了在运行时创建的 4 Function
s 个实例来保存每个 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);
}
}