1

在 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's 的情况下调用无参数方法。

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

4

3 回答 3

8

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

没有括号定义的方法不能用括号调用,所以method1万一只有一个选项。

method2以防万一, callt.method2()有两个重载选项可供选择,并且更def method2()合适。

有关如何选择最佳重载的精确描述,请参阅Scala 语言规范的第 6.26.3 节:规则非常简单且合乎逻辑。

于 2011-08-11T10:51:41.470 回答
3

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

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

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

所以如果你用一个参数在哪里调用它,编译器会选择这个,而不是可变参数版本。

于 2011-08-11T10:51:37.783 回答
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.

相反,不能使用参数列表调用可变参数方法。

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
              ^

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

在第二个示例中,第一个和最后一个案例是明确的。没有参数列表(如图所示)就不能调用可变参数,也不能在没有参数传递参数的情况下调用方法。可以调用一个没有参数的空参数列表的方法,这主要是为了使 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 的参数调用,则方法 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()是最具体的一个,因此被选中。

于 2011-08-11T20:05:00.617 回答