Kotlin作用域功能块是否有效内联



我正在编写一个Kotlin内联类,使Decimal4J在不实例化任何对象的情况下更方便。我担心作用域函数可能会创建lambda对象,从而使整个过程变得毫无意义。

考虑以下示例中的函数compareTo

/* imports and whatnot */
@JvmInline
value class Quantity(val basis: Long) {
companion object {
val scale: Int = 12
val metrics: ScaleMetrics = Scales.getScaleMetrics(scale)
val arithmetic: DecimalArithmetic = metrics.defaultArithmetic
}
operator fun compareTo(alt: Number): Int {
with(arithmetic) {
val normal = when (alt) {
is Double     -> fromDouble(alt)
is Float      -> fromFloat(alt)
is Long       -> fromLong(alt)
is BigDecimal -> fromBigDecimal(alt)
is BigInteger -> fromBigInteger(alt)
else          -> fromLong(alt.toLong())
}
return compare(basis, normal)
}
}
}

with(arithmetic)作用域是否在堆中创建lambda?kotlinglang.org上的文档一致地将作用域代码称为lambda表达式。有没有任何方法可以在不创建对象的情况下使用作用域函数?

所有内置的作用域函数,包括with,都标记为inline,这意味着实现将直接植入调用它的代码中。一旦发生这种情况,lambda调用就可以被优化掉。

更具体地说,这里是with的实现(去掉了Kotlin合同的内容,因为这与无关(

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}

扩展方法是,并且一直是,在编译时解析语法糖,所以这是有效的

public inline fun <T, R> with(receiver: T, block: (T) -> R): R {
return block(receiver) // (with `this` renamed by the compiler)
}

所以当我们调用时

operator fun compareTo(alt: Number): Int {
with (arithmetic) {
println("Hi :)")
println(foobar()) // Assuming foobar is a method on arithmetic
}
}

inline会将其转换为

operator fun compareTo(alt: Number): Int {
({
println("Hi :)")
println(it.foobar()) // Assuming foobar is a method on arithmetic
})(arithmetic)
}

任何称职的优化器都可以看到,这是一个可以立即评估的函数,所以我们现在应该继续这样做。我们最终得到的是

operator fun compareTo(alt: Number): Int {
println("Hi :)")
println(arithmetic.foobar()) // Assuming foobar is a method on arithmetic
}

这是你一开始应该写的。

所以,tl;博士,编译器足够聪明,可以算出它。你不必担心。这是用高级语言工作的好处之一。

顺便说一句,这不仅仅是抽象的。我只是在自己的机器上编译了上面的代码,然后反编译了JVM字节码,看看它到底做了什么。它的噪声相当大(因为JVM必然会有很多噪声(,但没有分配lambda对象,函数只是直接调用println两次。

如果你感兴趣,Kotlin以为例

fun compareTo(alt: Number): Unit {
return with(arithmetic) {
println("Hi :)")
println(foobar())
}
}

对于这个Java,经过反编译后,

public static final void compareTo-impl(long arg0, @NotNull Number alt) {
Intrinsics.checkNotNullParameter((Object)alt, (String)"alt");
long l = arithmetic;
boolean bl = false;
boolean bl2 = false;
long $this$compareTo_impl_u24lambda_u2d0 = l;
boolean bl3 = false;
String string = "Hi :)";
boolean bl4 = false;
System.out.println((Object)string);
int n = so_quant.foobar-impl($this$compareTo_impl_u24lambda_u2d0);
bl4 = false;
System.out.println(n);
}

有点吵,但想法完全一样。所有这些毫无意义的局部变量都将由一个好的JIT引擎处理。

只是一些额外的信息,以帮助澄清导致您混淆的术语。

单词"lambda"被定义为用于编写函数的语法。这个词并不描述函数本身,因此lambda这个词与是否分配函数对象无关。

在Kotlin中,可以选择多种不同的语法来定义或引用函数。Lambda只是其中之一。

// lambda assigned to variable
val x: (String) -> Unit = {
println(it)
}
// anonymous function assigned to variable
val y: (String) -> Unit = fun(input: String) {
println(input)
}
// reference to existing named function assigned to variable
val z: (String) -> Unit = ::println
// lambda passed to higher order function
“Hello World”.let { println(it) }
// anonymous function passed to higher order function
“Hello World”.let(fun(input: Any) { println(input) })
// reference to existing named function passed to higher order function
“Hello World”.let(::println)
// existing functional reference passed to higher order function
“Hello World”.let(x)

实际上,不存在可以传递的lambda对象。对象是一个可以使用上述任何语法定义的函数。一旦函数引用存在,用于创建它的语法就无关紧要了。

对于内联的高阶函数,就像标准库作用域函数一样,编译器完全优化了函数对象的创建。在我上面的例子中的四个高阶调用中,前三个将编译成相同的东西。最后一个有点不同,因为函数x已经存在,所以在内联代码中调用的将是x本身。它的内容不会在内联代码中被直接调用。

对高阶内联函数调用使用lambda语法的优点是,它使您能够对外部作用域(非本地返回(使用关键字,如returncontinuebreak

相关内容

最新更新