1

我期望这段代码(在精炼类型上使用模式匹配后调用匿名类的方法)

(new {
    def foo : Unit = println("Called foo method")
} : Any) match {
    case f : {def foo : Unit} ⇒
        println("Has foo method")
        f.foo
}

打印

Has foo method
Called foo method

(以及未经检查的警告)。

我知道由于类型擦除,匹配总是成功,但这不应该导致问题,因为运行时类型(即使考虑擦除)f应该是$anon$NameOfSomeAnonymousClassThatHasAfooMethod

当进入 Scala REPL (2.9.1) 时,它实际上会抛出NoSuchMethodException

<console>:11: warning: refinement AnyRef{def foo: Unit} in type pattern AnyRef{def foo: Unit} is unchecked since it is eliminated by erasure
              case f : {def foo : Unit} ⇒
                       ^
Has foo method
java.lang.NoSuchMethodException: $anon$1.foo()
        at java.lang.Class.getMethod(Class.java:1622)
        at .reflMethod$Method1(<console>:13)
        at .<init>(<console>:13)
        at .<clinit>(<console>:13)
        at .<init>(<console>:11)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
        at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
        at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
        at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
        at java.lang.Thread.run(Thread.java:679)

为什么?

编辑

事实证明,直接原因是foo生成为私有的。我在回答中推测了造成这种情况的原因,但我不确定。如果您有任何想法,请随时将其发布为答案!

4

3 回答 3

2

我知道匹配总是由于类型擦除而成功,但这不应该导致问题,因为 f 的运行时类型(即使考虑擦除)应该是 $anon$NameOfSomeAnonymousClassThatHasAfooMethod

从某种意义上说,它“应该”是显而易见的实现,并且是您所期望的;它不需要,正如你所发现的,它不需要。

细化上的模式匹配是盲目的。你必须有很大的信心。

这很奇怪,因为默认情况下 Scala 方法应该是公开的。

你在源代码中声明的方法默认是公开的。实施细节不是。

我怀疑原因是编译器错误地认为由于该类是匿名的,因此其定义的方法无法从类外部调用。

编译器正确地假定您必须违反安排条款才能直接调用匿名类的任何方法。你投你的参考,你抓住你的机会。

于 2012-07-23T08:51:26.860 回答
1

经过更多调查,我发现该方法以某种方式被设为私有:

(new {
    def foo : Unit = println("Called foo method")
} : Any) match {
    case f : {def foo : Unit} ⇒
        println("Has foo method")
        f.getClass.getDeclaredMethods
}

打印res5: Array[java.lang.reflect.Method] = Array(private void $anon$1.foo())

这很奇怪,因为默认情况下 Scala 方法应该是公开的。

正如 Edmondo1984 指出的那样,foo如果您删除: Any.

投机的

我怀疑原因是编译器错误地假设由于类是匿名的并且实例被声明为另一种类型,因此其定义的方法无法从类外部调用。这个假设在 Java 中是有效的,但在提供结构类型的语言中是无效的。因此,在过度热心地应用信息隐藏原则时,它将它们生成为私有的。如果是这样,这要么是编译器错误,要么是语言设计的极端情况(使用匿名函数和结构类型)。

于 2012-07-23T08:31:02.063 回答
1

正如我在评论中猜测的那样,问题在于,如果将匿名类向上转换为 Any,编译器会自动限制匿名定义方法的可见性。

(new {
    def foo : Unit = println("Called foo method")
} ) match {
    case f : {def foo : Unit} ⇒
        println("Has foo method")
        f.getClass.getDeclaredMethods
}

根据定义,您在不属于其任何超类的匿名类中创建的方法将仅在您刚刚创建的对象上可用。但是,如果您立即将对象向上转换为 Any,则不会有匿名类的类型安全实例,您将能够安全地调用方法 foo。

于 2012-07-23T09:46:00.797 回答