即使未在函数体中调用,也会计算中缀参数
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。)
我正在尝试使用
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。)