为什么没有为不同的返回类型定义方法重载?

时间:2009-10-17 16:44:36

标签: oop scala methods overloading

在Scala中,您可以通过使用共享公共名称但具有不同arities或不同参数类型的方法来重载方法。我想知道为什么这也没有扩展到方法的返回类型?请考虑以下代码:

class C {
  def m: Int = 42
  def m: String = "forty two"
}

val c = new C
val i: Int = C.m
val s: String = C.m

这有什么理由不起作用吗?

谢谢,

文森特。

6 个答案:

答案 0 :(得分:7)

实际上,你可以通过'隐含'的魔力使其发挥作用。如下:

scala> case class Result(i: Int,s: String)

scala> class C {
     |     def m: Result = Result(42,"forty two")
     | }

scala> implicit def res2int(res: Result) = res.i

scala> implicit def res2str(res: Result) = res.s

scala> val c = new C

scala> val i: Int = c.m

i: Int = 42


scala> val s: String = c.m

s: String = forty two

scala>

答案 1 :(得分:6)

主要原因是复杂性问题:使用“普通”编译器方法,从内到外(从内部表达式到外部范围),逐步构建二进制文件;如果添加仅返回类型的区分,则需要更改为回溯方法,这会大大增加编译时间,编译器复杂性(=错误!)。

另外,如果您返回一个可以自动转换为另一个的子类型或类型,您应该选择哪种方法?你会为完全有效的代码提供歧义错误。

不值得麻烦。

总而言之,您可以轻松地重构代码以避免仅返回类型的重载,例如通过添加要返回的类型的伪参数。

答案 2 :(得分:5)

当然,对于因返回类型而不同的方法,您可以进行重载,而不是因为返回类型的方法。例如,这很好:

def foo(s: String) : String = s + "Hello"
def foo(i: Int) : Int = i + 1

除此之外,你的问题的答案显然是一个设计决定:返回类型是方法签名的一部分,因为经历过AbstractMethodError的任何人都可以告诉你。

然而,考虑如何允许这样的重载可以与子类型一起使用:

class A {
  def foo: Int = 1
}
val a: A = //...lookup an A
val b = a.foo

这是完全有效的代码,javac将唯一解析方法调用。但是如果我将A子类化如下:

class B extends A {
  def foo: String = "Hello"
}

这会导致原始代码解析被调用的方法被破坏。 b应该是什么?我通过对某些现有类进行子类型化来逻辑地破坏了一些现有代码,即使我没有更改该代码或该类。

答案 3 :(得分:2)

我从未使用scala,所以如果我在这里错了,有人会打我的头,但这是我的看法。

假设您有两种方法,其签名因返回类型而异。

如果您正在调用该方法,编译器(解释器?)如何知道您实际想要调用哪种方法?

我确信在某些情况下它可能能够弄明白,但是,例如,如果你的一个返回类型是另一个的子类,那该怎么办?这并不容易。

Java不允许重载返回类型,并且因为scala是在java JVM上构建的,所以它可能只是一个java限制。

(编辑) 请注意,Covariant返回是一个不同的问题。覆盖方法时,您可以选择返回您应该返回的类的子类,但不能选择要返回的无关类。

答案 4 :(得分:1)

为了区分具有相同名称和参数类型但具有不同返回类型的不同函数,需要一些语法或分析表达式的站点。

Scala是一种面向表达式的语言(每个语句都是一个表达式)。通常,面向表达式的语言更喜欢使表达式的语义仅依赖于范围评估,而不是结果发生的情况,因此对于foo()i_take_an_int( foo() )中的表达式i_take_any_type ( foo())foo()作为一个语句都调用相同版本的foo()

还有一个问题是,通过返回类型将重载添加到具有类型推断的语言将使代码完全无法理解 - 您必须记住大量系统,以便预测代码获取时会发生什么执行。

答案 5 :(得分:1)

所有答案都说JVM不允许这样做是错误的。您可以根据返回类型重载。令人惊讶的是,JVM 确实允许这样做;它是编译器,用于在JVM上运行但不允许这样做的语言。但是有一些方法可以解决Scala中的编译器限制。

例如,请考虑以下代码段:

object Overload{
  def foo(xs: String*) = "foo"
  def foo(xs: Int*) = "bar"
}

这将引发编译器错误(因为varargs,在参数类型之后由*指示,键入erase to Seq):

Error:(217, 11) double definition:
def foo(xs: String*): String at line 216 and
def foo(xs: Any*): String at line 217
have same type after erasure: (xs: Seq)String
      def foo(xs: Any*) = "bar";

但是,如果您将第二个foo的值更改为3而不是bar(这样可以将返回类型从String更改为Int)如下:

object Overload{
  def foo(xs: String*) = "foo"
  def foo(xs: Int*) = 3
}

...您赢得获得编译错误。

所以你可以这样做:

val x: String = Overload.foo()
val y: Int = Overload.foo()

println(x)
println(y)

它会打印出来:

3
foo

但是,对此方法的警告是必须添加varargs作为重载函数的最后一个(或唯一)参数,每个参数都有自己的不同类型。

来源:http://www.drmaciver.com/2008/08/a-curious-fact-about-overloading-in-scala/