Kotlin 作用域函数块是否有效内联?

Wil*_*oom 2 primitive lambda inline 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Sil*_*olo 6

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

更具体地说,这是(删除了 Kotlin 合约内容,因为这与此处无关)的实现with

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
  return receiver.block()
}
Run Code Online (Sandbox Code Playgroud)

扩展方法始终是在编译时解析的语法糖,因此这实际上是有效的

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

所以当我们打电话时

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

将把inline它变成

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

任何称职的优化器都可以看到这是一个立即评估的函数,因此我们应该立即执行此操作。我们最终得到的是

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

这就是你一开始应该写的。

所以,tl;dr,编译器足够聪明,可以解决这个问题。你不必担心它。这是使用高级语言工作的好处之一。

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

如果您有兴趣,Kotlin 采用此示例函数

fun compareTo(alt: Number): Unit {
  return with(arithmetic) {
    println("Hi :)")
    println(foobar())
  }
}
Run Code Online (Sandbox Code Playgroud)

对于这个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);
}
Run Code Online (Sandbox Code Playgroud)

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