TLDR:Lambda 表达式不能有默认参数。如果您需要它们,您应该声明一个函数(可以在另一个函数内部)。
为了详细说明,让我们看看如何在 Kotlin 中定义类函数类型的不同方式。直觉上,人们会期望它们的工作原理相同,但它们的功能存在细微的差异。
1. 函数重载
当手动定义函数重载(Java 方式)时,可能不仅仅是call具有任何允许的参数编号的函数,而且store使用任意参数编号的类型中的函数引用。
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
2. 带默认参数的函数
Kotlin 中更惯用的做法是使用默认参数。虽然这看起来基本上等同于重载函数,但事实并非如此。显着的区别是:仅声明一个函数,并且类型推断仅在以下情况下才会考虑不同的参数计数:calling函数,但不是何时storing它通过函数引用。
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
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
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() }
这里的主要要点是,这 4 个可调用对象虽然支持相同的基本功能,但存在以下几点不同:
- Allows 声明和调用默认参数
- Allows storing通过考虑默认参数的函数引用
- 允许使用命名参数调用
通过将可调用对象引用为函数类型(这是匿名函数和 lambda 的唯一选项),您会丢失原始声明中存在的信息。