问题
给定一个简单的类层次结构
abstract class Base {}
class A extends Base {}
class B extends Base {}
还有一个类型类
trait Show[T] {
def show(obj: T): String
}
重载实现
class ShowBase extends Show[Base] {
override def show(obj: Base): String = "Base"
}
object ShowA extends ShowBase {
def show(obj: A): String = "A"
}
object ShowB extends ShowBase {
def show(obj: B): String = "B"
}
当执行下面的测试用例时
Seq((new A(), ShowA), (new B(), ShowB)).foreach {
case (obj, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
}
应该生成 (A,A)\n (B,B)
,但生成 (Base,A)\n (Base,B)
。
问题
这是怎么回事?不应调用具有最具体运行时类型的方法 - Polymorphism 101 ?
这个问题看起来类似于 another question其中类型参数阻止正确解析要调用的方法。但是,在我的例子中,类型参数化的 show
方法是随实际实现提供的,与另一个问题中的类型参数化方法形成对比。
天真的解决方案
扩展 ShowA
实现(ShowB
的类比):
object ShowA extends ShowBase {
def show(obj: A): String = "A"
override def show(obj: Base): String = {
require(obj.isInstanceOf[A], "Argument must be instance of A!")
show(obj.asInstanceOf[A])
}
}
给出预期的输出。问题是将 A
与 ShowB
混合使用会导致异常。
最佳答案
静态重载决议很容易推理:对于适用的方法,仅根据签名选择一个“更具体”的方法。
但是,
scala> Seq((new A(), ShowA), (new B(), ShowB))
res0: Seq[(Base, ShowBase)] = List((A@2b45f918,ShowA$@7ee4acd9), (B@57101ba4,ShowB$@6286d8a3))
在 ShowBase
中没有重载。
scala> res0 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
| }
java.lang.InternalError: Malformed class name
at java.lang.Class.getSimpleName(Class.java:1180)
at $anonfun$1.apply(<console>:17)
at $anonfun$1.apply(<console>:16)
at scala.collection.immutable.List.foreach(List.scala:383)
... 38 elided
哦,是的,不要使用 Scala 中的 getSimpleName
。
scala> res0 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass) }
(Base,class $line4.$read$$iw$$iw$A)
(Base,class $line5.$read$$iw$$iw$B)
好吧,
scala> class ShowBase extends Show[Base] {
| override def show(obj: Base): String = "Base"
| def show(a: A) = "A" ; def show(b: B) = "B" }
defined class ShowBase
scala> Seq((new A(), new ShowBase), (new B(), new ShowBase))
res3: Seq[(Base, ShowBase)] = List((A@15c3e01a,ShowBase@6eadd61f), (B@56c4c5fd,ShowBase@10a2918c))
scala> res3 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass) }
(A,class $line4.$read$$iw$$iw$A)
(B,class $line5.$read$$iw$$iw$B)
很容易想象一个宏使用重载方法 show
为给定接口(interface)生成部分函数。
另一种想法(不一定是好想法)是让编译器在运行时进行选择。
这目前很难在 REPL 中演示。您必须从散落在您的 REPL 历史记录中的对象中导入您想要使用的任何符号。 See the issue.
scala> def imps = $intp.definedSymbolList map (s => $intp.global.exitingTyper { s.fullName }) mkString ("import ", "\nimport ", "\n")
imps: String
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res15: Any = A
嘿,成功了!
或者,进入电源模式,将当前相位设置为 typer 并为您提供 intp
,但没有时髦的美元符号。因为我们真的需要更多的钱吗?
scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **
scala> def imps = intp.definedSymbolList map (_.fullName) mkString ("import ", "\nimport ", "\n")
imps: String
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res17: Any = A
如果您想查看未过滤的导入:
scala> intp.isettings.unwrapStrings = false
intp.isettings.unwrapStrings: Boolean = false
scala> imps
res11: String =
"import $line2.$read.$iw.$iw.$intp
import $line3.$read.$iw.$iw.Base
import $line3.$read.$iw.$iw.A
import $line3.$read.$iw.$iw.B
import $line4.$read.$iw.$iw.Show
import $line5.$read.$iw.$iw.ShowA
[snip]
再一次:
scala> abstract class Base ; class A extends Base ; class B extends Base
defined class Base
defined class A
defined class B
scala> trait Show[T <: Base] { def show(obj: T): String }
defined trait Show
scala> class ShowBase extends Show[Base] { override def show(obj: Base): String = "Base" }
defined class ShowBase
scala> object ShowA extends ShowBase { def show(obj: A): String = "A" }
defined object ShowA
scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **
scala> def imps = intp.definedSymbolList map (_.fullName) mkString ("import ", "\nimport ", "\n")
imps: String
scala> import tools.reflect._
import tools.reflect._
scala> val tb = reflect.runtime.currentMirror.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl@24e15d95
我有没有提到导入机制很尴尬?
scala> val a = new A
a: A = A@1e5b2860
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res0: Any = A
scala> ShowA show (a: Base)
res1: String = Base
scala> tb.eval(tb.parse(s"$imps ; ShowA show (a: Base)"))
res2: Any = Base
scala> val a: Base = new A
a: Base = A@7e3a93ce
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
scala.tools.reflect.ToolBoxError: reflective compilation has failed:
reference to a is ambiguous;
it is imported twice in the same scope by
import a
and import a
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:315)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.wrapInPackageAndCompile(ToolBoxFactory.scala:197)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:251)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:428)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:421)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.liftedTree2$1(ToolBoxFactory.scala:354)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:354)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:421)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:443)
... 37 elided
因此,如果您决定要选择哪种类型:
scala> val x: Base = new A
x: Base = A@2647e550
scala> tb.eval(tb.parse(s"$imps ; ShowA show x"))
res4: Any = Base
scala> tb.eval(tb.parse(s"$imps ; ShowA show (x.asInstanceOf[A])"))
res5: Any = A
关于Scala - 为什么不根据运行时类调用重载方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23091703/