即使未在函数体中调用,也会计算中缀参数

infix parameter getting calculated even when it is not called in function body

我正在尝试使用

infix fun <T> Boolean.then(param: T): T? = if (this) param else null

但它抛出 ArrayIndexOutOfBoundsException

(index > 0) then data[index - 1].id, where index == 0

因为

data[-1] doesn't exist.

我怎样才能让它在 Kotlin 中工作?

在 Kotlin 中,函数参数是急切求值的:当你调用一个函数时,每个参数的值都会在 将控制权传递给函数之前计算出来。无论该值是否会在函数中使用,都会发生这种情况。 (毕竟,一般来说,如果没有实际 运行 代码,你无法 判断 是否会使用它。*

(对于中缀函数和标准调用都是如此;尽管使用 different-looking 语法,但含义完全相同。)

事实上,对于大多数其他运算符也是如此:当您添加两个数字、连接两个字符串、return 来自函数的值或其他任何内容时,每个操作数都会首先被计算。只有少数例外,其中 short-circuiting &&|| 运算符最为明显。

因此,在您的情况下,诸如 then data[index - 1].id 之类的调用将始终先计算 data[index - 1].id,然后再将其传递给 then() 函数;因此,如果 index 为 0,则会抛出 ArrayIndexOutOfBoundsException,如您所见。

如果您不想对其进行评估,那么您必须改为传递 lambda,例如:

infix fun <T> Boolean.then(lazyValue: () -> T): T?
        = if (this) lazyValue() else null

那么你可以这样使用它:

(index > 0) then { data[index - 1].id }

发生的事情是对代码进行求值,给出一个 lambda,它作为 lazyValue 传递给函数;但 lambda 的 content 未被评估 unless/until* 它在函数中到达 lazyValue()

你可以在库函数中看到这种模式,例如require(value, lazyMessage);由于几乎总是会满足要求,并且消息通常是必须在运行时构造的复杂字符串,因此只有在条件为假时才评估其第二个参数,从而避免创建不必要的 String 对象。

传递 lambda 的 down-side 是它的效率略低:它需要创建一个对象来表示 lambda,因此会增加一些额外的 CPU 和堆。 (视情况而定,可能每次都需要创建一个新的对象,也可能会重复使用同一个对象。)

但 Kotlin 提供了一种解决方法:如果您将函数标记为 inline,那么它会避免函数调用和 lambda,并有效地将您的代码直接“粘贴”到函数,使其与“手动”编写函数体一样高效。


(* 有一个名为 contracts 的实验性功能可以让您告诉编译器您是否 确定在什么情况下 lambda将被评估。这可能会避免某些类型的警告或错误——尽管它不会改变评估顺序,所以你在这里仍然需要一个 lambda。)