为什么 Kotlin 不抱怨歧义?为什么要调用辅助构造函数?

Why isn't Kotlin complaining about ambiguity? And why is it calling the secondary constructor?

我正在用 Kotlin 测试一些东西,现在如果找不到答案我就睡不着了。

看看这个class:

data class Person(
    val name: String,
    val age: Int? = 0
) {
    constructor(
        secondName: String,
        secondAge: Int = 5
    ) : this(
        name = "$secondName 2",
        age = secondAge
    )
}

fun main() {
    val firstCase  = Person("Lorem")     // Complaints about ambiguity.
    val secondCase = Person("Lorem", 42) // Calls secondary constructor.
}

抱怨第一个Case的歧义是可以理解的,但是为什么第二个Case没有发生同样的事情呢?为什么它决定调用辅助构造函数而不是主构造函数?

现在,如果我在主构造函数上添加另一个参数具有默认值:

data class Person(
    val name: String,
    val age: Int? = 0,
    val isAdult: Boolean = false
) {
    constructor(
        secondName: String,
        secondAge: Int = 5
    ) : this(
        name = "$secondName 2",
        age = secondAge
    )
}

fun main() {
    val thirdCase = Person("Lorem") // Calls secondary constructor.
}

我原以为第三个案例会有歧义,就像第一个案例一样。但不是!它调用辅助构造函数。为什么?!

Kotlin 更优先考虑更具体的函数。考虑这个例子:

foo(dog) // invokes foo(Dog)

fun foo(animal: Animal) {}
fun foo(dog: Dog) {}

这里的foo(dog)也是有歧义的,其实是用了foo(Dog)的功能。要使用另一个,我们必须显式向上转换:

foo(dog as Animal)

因为 IntInt? 的子类型,您的辅助构造函数比主构造函数更具体,这就是首选它的原因。

Kotlin(和 Java)中 method resolution 的规则可能有点神秘。值得庆幸的是,他们几乎在所有情况下都会做显而易见的事情——这显然是重点! — 但也有一些令人惊讶的极端情况。

一般原则是它会选择可以匹配的最具体方法,并且只有在没有一个获胜者时才给出错误。

所以在你的第二种情况下,参数是 StringInt。候选人是主构造函数(取StringInt?)和副构造函数(取StringInt)。后者是更具体的匹配,因为 IntInt? 的子类型,所以它选择那个。

但在您的第一种情况下,提供的唯一参数是 String,它同样匹配两个构造函数,因此没有明确的赢家,它标记了歧义。

你的第三个案例更不明显。但是,讨论如何从所有重载中选择最具体的候选人的 section of the Kotlin spec 说:

For each candidate we count the number of default parameters not specified in the call (i.e., the number of parameters for which we use the default value). The candidate with the least number of non-specified default parameters is a more specific candidate

我认为这就是你的第三种情况发生的事情:它选择辅助构造函数(它只留下一个参数及其默认值)而不是主要构造函数(它会留下两个)。

在您的第一个示例中,当您使用两个参数时没有歧义,因为它会选择与您的参数匹配的最具体类型的构造函数。 IntInt? 之间的选择没有歧义,因为 IntInt?.

更具体

当您提供单个参数时,它是模棱两可的,因为您没有提供任何可以帮助区分您是想要 Int 还是 Int?.

的参数