Kotlin 中 lambda 表达式中的默认参数

Mer*_*kiz 8 lambda kotlin

我想创建一个 lambda 并将其分配给一个变量,以下按预期工作:

val rollDice = { min: Int, max: Int -> (min..max).random() }
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试为参数分配默认值时,出现错误:

val rollDice = { min: Int = 1, max: Int = 12 -> (min..max).random() }
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Unexpected tokens (use ';' to separate expressions on the same line)
Run Code Online (Sandbox Code Playgroud)

是否无法在 Kotlin 中为 lambda 表达式中的参数分配默认值?

The*_*tor 18

TLDR: Lambda 表达式不能有默认参数。如果你需要它们,你应该声明一个函数(可以在另一个函数内部)。


为了详细说明,让我们看看如何在 Kotlin 中定义类函数类型的不同方式。直觉上,人们会期望它们以相同的方式工作,但它们的功能存在细微的差异。

1. 重载函数

手动定义函数重载时(Java 方式),不仅可以使用任何允许的参数编号调用函数,还可以使用任何参数编号将函数引用存储在类型中。

fun overload(min: Int, max: Int) = (min..max).random()
fun overload(min: Int) = overload(min, 12)
fun overload() = overload(1, 12)

// Calling is possible with all numbers of arguments, and naming ones at the end
overload()
overload(3)
overload(min=3)
overload(3, 4)
overload(3, max=4)
overload(min=3, max=4)

// Intuitively, all 3 ways of storing work:
val f: (Int, Int) -> Int = ::overload
val g: (Int) -> Int = ::overload
val h: () -> Int = ::overload

// On the other hand, this does NOT compile because of ambiguity:
val i = ::overload
Run Code Online (Sandbox Code Playgroud)

2. 带默认参数的函数

Kotlin 中更惯用的是使用默认参数。虽然这似乎主要等同于重载函数,但事实并非如此。显着的区别是:只声明了一个函数,类型推断只会在调用函数时考虑不同的参数计数,而在通过函数引用存储时不会。

fun default(min: Int = 1, max: Int = 12) = (min..max).random()

// Calling is possible exactly like overloaded functions
default()
default(3)
default(min=3)
default(3, 4)
default(3, max=4)
default(min=3, max=4)

// No ambiguity, f and g have the same type (all parameters)
val f = ::default
val g: (Int, Int) -> Int = ::default

// However, storing in a function type taking fewer arguments is NOT possible
val h: (Int) -> Int = ::default
val i: () -> Int = ::default
Run Code Online (Sandbox Code Playgroud)

3. 匿名函数

匿名函数即使在声明中也不允许使用默认参数,因此只有一种调用它们的方法。此外,存储它们的变量是函数类型的,这会丢失有关参数名称的信息,从而防止使用命名参数进行调用。

val anonymous = fun(min: Int, max: Int) = (min..max).random()
val anonymous: (Int, Int) -> Int = fun(min: Int, max: Int) = (min..max).random()

// Only one way to call
anonymous(3, 4)

// No ambiguity, f and g have the same (full type)
val f = anonymous
val g: (Int, Int) -> Int = anonymous

// Mistake, which compiles: this declares h as a *property*,
// with type KProperty<(Int, Int) -> Int>
val h = ::anonymous

// Calling with named arguments is NOT possible
anonymous(3, 4)         // OK
anonymous(min=3, max=4) // error
Run Code Online (Sandbox Code Playgroud)

4. Lambda 表达式

与匿名函数一样,lambda 表达式不允许使用默认参数,也不能使用命名参数调用。由于它们立即存储为类似 的函数类型(Int, Int) -> Int,因此它们受到与引用实际函数的函数类型相同的限制。

只有在 lambda 表达式或要分配给的函数类型中指定了参数类型时,类型推断才有效:

// OK:
val lambda = { min: Int, max: Int -> (min..max).random() }
val lambda2: (Int, Int) -> Int = { min, max -> (min..max).random() }

// Type inference fails:
val lambda3 = { min, max -> (min..max).random() }
Run Code Online (Sandbox Code Playgroud)

这里的主要内容是这 4 个可调用对象虽然支持相同的基本功能,但在以下几点有所不同:

  • 允许声明和调用默认参数
  • 允许通过考虑默认参数的函数引用进行存储
  • 允许使用命名参数调用

通过将可调用对象称为函数类型(这是匿名函数和 lambda 的唯一选项),您会丢失原始声明中存在的信息。

  • 这是一个非常深入的解释。多谢! (2认同)