默认参数vs重载,何时使用哪个
在Kotlin中,有两种方法可以通过指定默认参数值来表示可选参数:
fun foo(parameter: Any, option: Boolean = false) { ... }
或通过引入过载:
fun foo(parameter: Any) = foo(parameter, false) fun foo(parameter: Any, option: Boolean) { ... }
在哪种情况下首选哪种方式?
这种功能的消费者有什么区别?
在调用其他Kotlin代码的Kotlin代码中,可选参数往往是使用重载的标准。 使用可选参数应该是你的默认行为。
使用默认值的特殊情况:
-
作为一般的做法,或者如果不确定 – 使用默认参数覆盖。
-
如果要让调用者看到默认值,请使用默认值。 他们将在IDE工具提示( 即Intellij IDEA )中显示,并让调用者知道他们正在作为合同的一部分应用。 你可以在下面的屏幕截图中看到,如果
x
和y
被忽略,调用foo()
会默认一些值:而用函数重载做同样的事情隐藏了这些有用的信息,只是呈现得更加混乱:
-
使用默认值会导致两个函数的字节码生成,其中一个指定了所有参数,另一个则是可以使用缺省值检查和应用缺少参数的桥接函数。 无论您有多少个违约参数,它总是只有两个函数。 因此,在一个总功能数量受限的环境( 即Android )中,最好只有这两个函数,而不是完成相同工作所需的大量重载。
您可能不想使用默认参数值的情况:
-
当您希望另一个JVM语言能够使用默认值时,您需要使用显式重载或使用
@JvmOverloads
注释 :对于每个具有默认值的参数,这将会生成一个额外的重载,该参数将删除参数列表中的该参数及其右侧的所有参数。
-
你有一个以前版本的库和二进制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) = ...
-
你有一些可能没有意义的论点,因此超载可以将参数分组为有意义的协调集。
-
使用默认值调用方法必须执行另一步来检查缺少哪些值并应用默认值,然后将调用转发给实际方法。 因此,在一个性能受限的环境中(例如Android,嵌入式,实时的,在方法调用上的十亿次循环迭代 ),这个额外的检查可能是不希望的。 虽然如果在配置文件中没有看到问题,这可能是一个虚构的问题,可能由JVM内联,可能根本没有任何影响。 先担心,再测量一下。
不支持这两种情况的案例:
如果你正在阅读有关这个从其他语言的一般论据…
-
在这个类似的问题的C#答案尊敬的Jon Skeet提到你应该小心使用默认值,如果他们之间可以更改构建之间,这将是一个问题。 在C#中,默认是在调用站点,而在Kotlin中,非内联函数是在被调用的(桥)函数内。 因此,对于Kotlin来说,改变隐藏和显式的价值违约是同样的影响,这个论点不应该影响决策。
-
也在C#答案中说,如果团队成员对使用违约论点持有相反的观点,那么也许不要使用它们。 这不应该适用于Kotlin,因为它们是核心语言特性,并且自1.0之前就在标准库中使用,并且不支持限制它们的使用。 对方团队成员应该默认使用默认的论点,除非他们有明确的情况,使他们无法使用。 而在C#中,这个语言的生命周期后来被引入,因此有了更多的“可选收养”
当参数省略时,函数的实现变得更简单,重载可能是首选。
考虑下面的例子:
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)
让我们来看看如何在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
。 它还有两个额外的参数:Int
掩码,指定哪些参数实际上已经传递,还有一个Object
参数,目前没有使用,但保留允许将来使用默认参数的超级调用。
当在呼叫站点省略某些参数时,该桥被调用。 网桥分析掩码,为省略的参数提供默认值,然后调用现在指定所有参数的第一个方法。
将@JvmOverloads
注释放在函数上时,会生成其他重载,每个参数具有默认值一个。 所有这些重载委托给foo$default
桥。 对于foo
函数,将生成以下附加的重载: foo(Ljava/lang/Object;)V
。
因此,从方法计数的角度来看,在一个函数只有一个带有默认值的参数的情况下,不管是使用重载还是默认值,你都会得到两个方法。 但是,如果有多个可选参数,则使用默认值而不是重载将导致生成的方法更少。