Scala 的方法重载:使用或不使用括号定义的可变参数和无参数方法之间的细微功能差异背后的原因?

发布于 2024-11-29 07:40:06 字数 948 浏览 4 评论 0 原文

在处理重载方法时,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 的情况下,将调用无参数方法。

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

Encountered a weirdness in Scala (2.8.1) in handling an overloaded method where the first is a no-args one and the second takes a variable number of arguments (0..N). Test code:

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)
  }
}

By compiling & running it, the output is:

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)

So if running method1 with parenthesis and zero arguments we get to the varargs method, but in method2's case the no-args method is invoked.

What is the explanation or reasoning behind this weird behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

2024-12-06 07:40:06

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

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

method2 情况下,调用 t.method2() 有两个重载选项可供选择,而 def method2() 更适合。

参见Scala 语言规范 详细描述了如何选择最佳重载:规则非常简单且符合逻辑。

Ok, then the question could be reformulated as "why would someone think the behavior is logical?" :)

A method defined without parentheses can't be called with parentheses, so in method1 case there is only one option.

In method2 case, call t.method2() has two overloaded options to choose from, and def method2() is a better fit.

See Section 6.26.3 of The Scala Language Specification for a precise description of how the best overload is chosen: the rules are quite simple and logical.

岁月苍老的讽刺 2024-12-06 07:40:06

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

我还没有测试过它,但我很确定,您可以更进一步并重载第二个方法,如下所示:

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

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

It is actually very logical. The compiler pattern matches on method signatures and takes the one that matches the best.

I have not tested it, but I'm pretty sure, that you could go a step further and overload the second method like this:

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

So if you where to call it with one argument, the compiler would chose this one, instead of the varargs version.

我喜欢麦丽素 2024-12-06 07:40:06

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

scala> def method1 = 0
method1: Int

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

请注意,错误来自 Scala 尝试对 method1 的结果调用 apply(),该结果是一个 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 验证其中一个是否与另一个一样具体。有四个根据类型和表达式定义的简单规则,但其要点是,如果可以使用 a 的参数调用 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() 是最具体的一个,因此选择它。

Well, method1() cannot be the def method = declaration, since it is not possible to call a method without a parameter list passing a parameter list.

scala> def method1 = 0
method1: Int

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

Note that the error comes from Scala trying to call apply() on the result of method1, which is an Int.

Conversely, one cannot call a vararg method with a parameter list.

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
              ^

Therefore, in the first case, there's no other possible behavior than what was shown.

In the second example, the first and last cases are non-ambiguous. One can't call a vararg without a parameter list (as shown), and one can't call a method without parameters passing a parameter. One can call a method with an empty parameter list without parameters, which was done mostly to make Java APIs more "pretty" -- for example, .toString.

The second case -- calling the method with an empty parameter list -- introduces some ambiguity. Scala then tries to determine if one is more specific than the other.

Let's take a brief detour here. Why check for more specific methods? Suppose you have this:

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

This kind of thing is relatively common on Java APIs. If someone called f("abc"), one naturally expects the compiler to call the second method, not the first, but both are valid. So, how to disambiguate them? Perhaps, since String is a perfect match for what is being passed, one might use that, right? Well, consider, then, this:

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

And I call it with f(new ArrayList[String]) So, what now? Well, AnyRef is a class, while Collection is an interface, so we might give precedence to interfaces over classes? And what if I had another method expecting List -- that's also an interface.

So, to solve this ambiguity, Scala uses the most specific concept. That is actually a pretty easy concept to apply.

First, Scala verifies is one is as specific as the other. There are four simple rules defined in terms of types and expressions, but the gist of it is that method a is as specific as method b if b can be called with a's parameters.

In this case, method2() is as specific as method2(args: Any*), because method2(args: Any*) can be called with an empty parameter list. On the other hand, method2(args: Any*) is not as specific as method2(), because method2() cannot be called with arguments of type Any.

Next, Scala verifies if one the class that defines one of the methods is a subclass of the class that defines the other. This rule does not apply for this case.

Finally, Scala adds 1 to each method for each of the two criteria above it fits. So method2() has weight 1, and method2(args: Any*) has weight 0. If the weight of one is greater than the other, that method is considered to be the most specific.

As we saw, method2() is the most specific one, so it is chosen.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文