【问题标题】:Why does this throw `NoSuchMethodException` when such a method exists?当这样的方法存在时,为什么会抛出`NoSuchMethodException`?
【发布时间】:2012-07-23 08:02:53
【问题描述】:

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

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

【问题讨论】:

  • stackoverflow.com/questions/3200301/… 不是原因,因为这里 foo 是公开的。
  • 你确定这是修改后的名字吗?我会打印所有的getMethods 来检查。
  • 不完全受欢迎的建议,我知道,但我会避开结构类型:-s
  • @PeterLawrey:如果我使用f.getClass.getMethods foreach println,它只会列出Object 方法。但是为什么它给出了错误的课程呢?

标签: java scala reflection jvm anonymous-class


【解决方案1】:

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

它“应该”的意思是它是显而易见的实现,它是你所期望的;它不需要,正如你所发现的,它不需要。

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

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

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

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

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

【讨论】:

  • foo “在源中声明的方法”,而不是实现细节。
  • 如果演员阵容不好,那就是ClassCastException,而不是NoSuchMethodException
  • 如果编译器确定代码违反了安排的条款,那不应该是 compile-time 错误吗?
  • foo 不是任何可访问类的成员。我不是在严格意义上的“jvm 级别转换”中使用“转换”,而是说“你正在推翻编译器并且你得到了你所得到的”。这也是#3 的答案。一旦你下注,所有的赌注都结束了。编译器不会阻止你。你可能可以相信我,因为我写了很多相关代码。
  • 我投票认为这是一个错误,NoSuchMethodException 对于静态类型的语言是不可接受的。如果无法修复,为什么完全允许结构类型匹配?
【解决方案2】:

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

(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 指出的那样,如果您删除 : Any,它就可以工作(foo 方法是公开的)。

推测

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

【讨论】:

  • 其实,现在我怀疑是不是这个原因。如果是这样,new {def foo = 123}.foo 应该会失败,但实际上它会按预期工作(返回 123)。
  • @Edmondo1984:现在foo 生成为公开的。这就解释了。
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 1970-01-01
    • 2019-12-03
    相关资源
    最近更新 更多