默认参数vs重载,何时使用哪个

时间:2016-09-27 20:35:07

标签: overloading kotlin optional-parameters

在Kotlin中,有两种表达可选参数的方法,可以通过指定默认参数值:

fun foo(parameter: Any, option: Boolean = false) { ... }

或引入过载:

fun foo(parameter: Any) = foo(parameter, false)
fun foo(parameter: Any, option: Boolean) { ... }

在哪种情况下首选哪种方式?

这种功能的消费者有什么不同?

3 个答案:

答案 0 :(得分:9)

在Kotlin代码中调用其他Kotlin代码可选参数往往是使用重载的常态。使用可选参数应该是默认行为。

使用默认值的特殊情况:

  • 作为一般惯例或不确定 - 使用默认参数覆盖覆盖。

  • 如果您希望调用者看到默认值,请使用默认值。它们将显示在IDE工具提示(即Intellij IDEA )中,并让调用者知道它们是作为合同的一部分应用的。您可以在以下屏幕截图中看到,如果省略foo()x的值,则调用y将默认某些值:

    enter image description here

    虽然使用函数重载执行相同的操作会隐藏这些有用的信息并且只会呈现出更加混乱:

    enter image description here

  • 使用默认值会导致两个函数的字节码生成,一个指定了所有参数,另一个是桥函数,可以检查并应用缺省参数及其默认值。无论您有多少默认参数,它总是两个函数。因此,在总功能计数约束环境(即Android )中,最好只使用这两个函数,而不是完成相同工作所需的大量重载。 / p>

您可能不想使用默认参数值的情况:

  • 当您希望其他JVM语言能够使用默认值时,您需要使用显式重载或使用@JvmOverloads annotation

      

    对于每个具有默认值的参数,这将生成一个额外的重载,该参数具有此参数,并且参数列表中右侧的所有参数都已删除。

  • 您拥有以前版本的库,并且对于二进制API兼容性,添加默认参数可能会破坏现有编译代码的兼容性,而添加重载则不会。

  • 您有以前的现有功能:

    fun foo() = ...
    

    您需要保留该功能签名,但您还希望添加另一个具有相同签名但附加可选参数:

    fun foo() = ...
    fun foo(x: Int = 5) = ...   // never can be called using default value
    

    您将无法使用第二个版本中的默认值(通过反射callBy除外)。相反,所有不带参数的foo()调用仍然会调用该函数的第一个版本。所以你需要使用不带默认值的不同重载,否则你会混淆函数的用户:

    fun foo() = ...  
    fun foo(x: Int) = ...
    
  • 您的参数可能没有意义,因此重载允许您将参数分组为有意义的协调集。

  • 使用默认值调用方法必须执行另一步以检查缺少哪些值并应用默认值,然后将调用转发给real方法。因此,在性能受限的环境中(即Android,嵌入式,实时,方法调用上的十亿次循环迭代),可能不需要这种额外的检查。虽然如果你没有看到分析中的问题,这可能是一个想象的问题,可能由JVM内联,并且可能根本没有任何影响。在担心之前先测量。

不支持这两种情况的案例

如果您正在阅读其他语言的一般性论点......

  • C# answer for this similar question尊敬的Jon Skeet中提到,如果他们可以在构建之间进行更改,那么您应该小心使用默认值,这将是一个问题。在C#中,默认是在调用站点,而在Kotlin中,对于非内联函数,它在被调用的(桥)函数内部。因此,对于Kotlin来说,它对改变隐藏和显式违约值的影响是相同的,这个论点不应该影响决策。

  • 也在C#的答案中说,如果团队成员对使用默认论点持反对意见,那么可能不会使用它们。这不应该应用于Kotlin,因为它们是核心语言特性,并且在1.0之前的标准库中使用,并且不支持限制它们的使用。对方团队成员应默认使用默认参数,除非他们有明确的案例使其无法使用。然而在C#中,它在该语言的生命周期中被引入很久,因此有了更多的选择性采用的感觉"

答案 1 :(得分:3)

让我们来看看如何在Kotlin中编译带有默认参数值的函数,以查看方法计数是否存在差异。它可能因目标平台而异,因此我们将首先考虑Kotlin for JVM。

对于函数fun foo(parameter: Any, option: Boolean = false),将生成以下两种方法:

  • 首先是foo(Ljava/lang/Object;Z)V,当在呼叫站点指定所有参数时调用它。
  • 其次是synthetic bridge foo$default(Ljava/lang/Object;ZILjava/lang/Object;)V。它有2个额外的参数:Int掩码,用于指定实际传递的参数和当前未使用的Object参数,但保留用于允许将来使用默认参数的超级调用。

在调用站点省略某些参数时调用该桥。桥分析掩码,为省略的参数提供默认值,然后调用现在指定所有参数的第一个方法。

当您在函数上放置@JvmOverloads注释时,会生成其他重载,每个参数对应一个默认值。所有这些重载都委托给foo$default桥。对于foo函数,将生成以下额外重载:foo(Ljava/lang/Object;)V

因此,从方法计数的角度来看,在函数只有一个参数具有默认值的情况下,无论是使用重载还是默认值,都会得到两种方法。但是,如果有多个可选参数,则使用默认值而不是重载将导致生成的方法更少。

答案 2 :(得分:2)

当省略参数时,当函数的实现变得更简单时,可能首选过载。

考虑以下示例:

fun compare(v1: T, v2: T, ignoreCase: Boolean = false) =
    if (ignoreCase) 
        internalCompareWithIgnoreCase(v1, v2) 
    else
        internalCompare(v1, v2)

当它被调用为compare(a, b)ignoreCase被省略时,实际上你没有使用ignoreCase支付两次:首先是检查参数时的默认值而不是省略的值第二个是当您检查ignoreCase正文中的compare并根据其值转移到internalCompare时。

添加过载将消除这两项检查。 JIT编译器也更有可能使用这种简单主体的方法。

fun compare(v1: T, v2: T) = internalCompare(v1, v2)