【问题标题】:Why is method overloading not defined for different return types?为什么没有为不同的返回类型定义方法重载?
【发布时间】:2010-12-07 15:29:55
【问题描述】:

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

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

这有什么不应该起作用的原因吗?

谢谢,

文森特。

【问题讨论】:

标签: oop scala methods overloading


【解决方案1】:

实际上,您可以通过“隐式”的魔力使其工作。如下:

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>

【讨论】:

    【解决方案2】:

    您当然可以对返回类型不同的方法进行重载,但不能返回类型不同的方法。例如,这很好:

    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 应该是什么?尽管我没有更改该代码或该类,但我通过子类型化某些现有类在逻辑上破坏了一些现有代码。

    【讨论】:

    • a 的静态类型是 A,而 A 只有一个名为 foo 的方法(返回 Int 的方法),那么歧义在哪里?
    【解决方案3】:

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

    另外,如果你返回一个子类型或者一个可以自动转换为另一个的类型,你应该选择哪种方法呢?对于完全有效的代码,您会给出模棱两可的错误。

    不值得麻烦。

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

    【讨论】:

      【解决方案4】:

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

      假设您有两个方法,它们的签名仅在返回类型上有所不同。

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

      我确信在某些情况下它可能能够解决,但是如果,例如,您的一种返回类型是另一种的子类怎么办?这并不总是那么容易。

      Java 不允许重载返回类型,而且由于 scala 是基于 java JVM 构建的,因此可能只是 java 的限制。

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

      【讨论】:

        【解决方案5】:

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

        Scala 是一种面向表达式的语言(每个语句都是一个表达式)。一般来说,面向表达式的语言更喜欢让表达式的语义只依赖于范围评估发生,而不是结果发生了什么,所以对于 i_take_an_int( foo() )i_take_any_type ( foo())foo() 中的表达式 foo() 作为语句都调用相同版本的foo()

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

        【讨论】:

          【解决方案6】:

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

          例如,考虑下面的sn-p代码:

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

          这会抛出编译器错误(因为varargs,由参数类型后面的*表示,类型擦除到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
          

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

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

          【讨论】:

            猜你喜欢
            • 2011-02-14
            • 1970-01-01
            • 2019-09-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-01-09
            • 1970-01-01
            • 2015-06-10
            相关资源
            最近更新 更多