Scala的方法重载:与使用或不使用括号定义的varargs和no-args方法的微妙功能差异背后的原因?

时间:2011-08-11 10:26:47

标签: scala overloading parentheses

在处理重载方法时遇到Scala(2.8.1)中的一个奇怪现象,其中第一个是无参数,第二个采用可变数量的参数(0..N)。测试代码:

class Test {
  def method1 = println("method1 invoked with zero args")
  def method1(args: Any*) = println("method1 with args " + args)

  def method2() = println("method2 invoked with zero args")
  def method2(args: Any*) = println("method2 with args " + args)
}

object Test {
  def main(args: Array[String]) {
    val t = new Test
    t.method1
    t.method1()
    t.method1(1,2,3)
    println
    t.method2
    t.method2()
    t.method2(1,2,3)
  }
}

编译&运行它,输出是:

method1 invoked with zero args
method1 with args WrappedArray()
method1 with args WrappedArray(1, 2, 3)

method2 invoked with zero args
method2 invoked with zero args
method2 with args WrappedArray(1, 2, 3)

因此,如果使用括号和零参数运行method1,我们将使用varargs方法,但在method2的情况下,将调用no-args方法。

这种奇怪行为背后的解释或推理是什么?

3 个答案:

答案 0 :(得分:8)

  

好的,那么这个问题可以重新表述为“为什么会有人认为这种行为是合乎逻辑的?” :)

使用括号不能调用没有括号定义的方法,因此在method1情况下只有一个选项。

method2案例中,致电t.method2()有两个可供选择的重载选项,def method2()更适合。

参见The Scala Language Specification的第6.26.3节,以准确描述如何选择最佳过载:规则非常简单和合乎逻辑。

答案 1 :(得分:3)

这实际上很合乎逻辑。编译器模式匹配方法签名并采用匹配最佳的那个。

我还没有测试过,但我很确定,你可以更进一步,重载第二种方法:

def method2(arg: Any) = println("method2 invoked with a single arg " + arg)

因此,如果您使用一个参数调用它,编译器将选择此参数,而不是varargs版本。

答案 2 :(得分:1)

嗯,method1()不能是def method =声明,因为如果没有参数列表传递参数列表,就无法调用方法。

scala> def method1 = 0
method1: Int

scala> method1()
<console>:9: error: Int does not take parameters
              method1()
                     ^

请注意,错误来自Scala尝试在apply()的结果上调用method1,这是Int

相反,人们不能使用参数列表调用vararg方法。

scala> def vararg(x: Int*) = x.size
vararg: (x: Int*)Int

scala> vararg
<console>:9: error: missing arguments for method vararg;
follow this method with `_' if you want to treat it as a partially applied function
              vararg
              ^

因此,在第一种情况下,除了显示的内容之外,没有其他可能的行为。

在第二个例子中,第一个和最后一个案例是非模糊的。如果没有参数列表(如图所示),则无法调用vararg,并且如果没有参数传递参数,则无法调用方法。一个可以使用没有参数的空参数列表调用方法,这主要是为了使Java API更“漂亮” - 例如,.toString

第二种情况 - 使用空参数列表调用方法 - 引入了一些歧义。然后,Scala会尝试确定一个是否比另一个更具体

我们在这里简单地绕道而行。为什么要检查更具体的方法?假设你有这个:

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: String) = x.length
}

这种事情在Java API上比较常见。如果某人调用f("abc"),则自然希望编译器调用第二种方法,而不是第一种方法,但两者都是有效的。那么,如何消除它们的歧义呢?或许,因为String与传递的内容完全匹配,所以可以使用它,对吧?那么,请考虑一下:

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: java.util.Collection[_]) = x.size
}

我称之为f(new ArrayList[String])那么,现在呢?那么,AnyRef是一个类,而Collection是一个接口,所以我们可能优先考虑类的接口?如果我有另一种期望List的方法 - 那也是一个接口。

因此,为了解决这种歧义,Scala使用最具体的概念。这实际上是一个非常简单的概念。

首先,Scala验证一个是特定于另一个。根据类型和表达式定义了四个简单的规则,但它的要点是,如果可以使用参数调用b,则方法a与方法b一样具体。

在这种情况下,method2()method2(args: Any*)一样具体,因为可以使用空参数列表调用method2(args: Any*)。另一方面,method2(args: Any*)并不像method2()那样具体,因为method2()无法使用Any类型的参数调用。

接下来,Scala验证定义其中一个方法的类是否是定义另一个方法的类的子类。此规则不适用于此案例。

最后,Scala为上述两个标准中的每个标准的每个方法添加1。因此method2()的权重为1,而method2(args: Any*)的权重为0.如果一个权重大于另一个,则该方法被认为是最具体的

正如我们所见,method2()是最具体的一个,所以选择它。