当这样的方法存在时,为什么抛出`NoSuchMethodException`?

时间:2012-07-23 08:02:53

标签: java scala reflection jvm anonymous-class

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

(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被生成为私有。我在答案中推测了这个原因,但我不确定。如果您有任何想法,请随时将其作为答案发布!

3 个答案:

答案 0 :(得分:2)

  

我知道匹配总是因为类型擦除而成功,但那样   不应该导致问题,因为运行时类型(即使考虑   删除f应该是   $ $不久NameOfSomeAnonymousClassThatHasAfooMethod

从某种意义上讲它应该是明显的实现,而这正是你所期望的;它不需要,而且正如你所发现的那样,它没有。

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

  

这很奇怪,因为Scala方法默认是公开的。

默认情况下,您在源中声明的方法是公共的。实施细节不是。

  

我怀疑原因是编译器错误地认为是   该类是匿名的,其定义的方法是不可调用的   课外。

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

答案 1 :(得分: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中是有效的,但在提供结构类型的语言中则不然。因此,它过分热衷于information hiding的原则,将它们作为私人产生。如果是这样,这可能是编译器错误或语言设计角落情况(使用匿名函数和结构类型)。

答案 2 :(得分: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。