【问题标题】:Using Structural Type in Scala occurs NoSuchMethodException在 Scala 中使用结构类型发生 NoSuchMethodException
【发布时间】:2017-07-12 06:25:16
【问题描述】:

我正在编写一个带有三个参数ffromto 的函数。 f 应该是任何具有 apply 方法的对象,它消耗并产生一个 Int。

 def printValues(f: {def apply(n: Int):Int}, from: Int, to: Int) {
   for(i <- from to `to`) print(f(i) + " ")
   print("\n")
 }

我在这里使用结构类型来保证fapply() 方法。

当我使用Array[Int] 调用方法printValues() 时,一切顺利。

printValues(Array(1,1,2,3,5,8,13,21,34,55), 3, 6)

我尝试使用 lambda 表达式调用该方法,结果一团糟

printValues((x: Int) => x * x, 3, 6)

错误信息

java.lang.NoSuchMethodException: ch18.p8.Main$$$Lambda$97/474675244.apply(int)
    at java.lang.Class.getMethod(Class.java:1786)
    at ch18.p8.Main$.reflMethod$Method3(Ch18.scala:270)
    at ch18.p8.Main$.$anonfun$printValues$1(Ch18.scala:270)
    at scala.runtime.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.java:12)
    at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
    at scala.collection.immutable.Range.foreach(Range.scala:156)
    at scala.collection.TraversableLike.map(TraversableLike.scala:234)
    at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at ch18.p8.Main$.printValues(Ch18.scala:270)
    at ch18.p8.Main$.delayedEndpoint$ch18$p8$Main$1(Ch18.scala:274)
    at ch18.p8.Main$delayedInit$body.apply(Ch18.scala:267)
    at scala.Function0.apply$mcV$sp(Function0.scala:34)
    at scala.Function0.apply$mcV$sp$(Function0.scala:34)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App.$anonfun$main$1$adapted(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:389)
    at scala.App.main(App.scala:76)
    at scala.App.main$(App.scala:74)
    at ch18.p8.Main$.main(Ch18.scala:267)
    at ch18.p8.Main.main(Ch18.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.reflect.internal.util.ScalaClassLoader.$anonfun$run$2(ScalaClassLoader.scala:98)
    at scala.reflect.internal.util.ScalaClassLoader.asContext(ScalaClassLoader.scala:32)
    at scala.reflect.internal.util.ScalaClassLoader.asContext$(ScalaClassLoader.scala:30)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:129)
    at scala.reflect.internal.util.ScalaClassLoader.run(ScalaClassLoader.scala:98)
    at scala.reflect.internal.util.ScalaClassLoader.run$(ScalaClassLoader.scala:90)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:129)
    at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:22)
    at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:21)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:28)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:61)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:88)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:99)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:104)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

我尝试验证对象 lambdaxxx 具有函数 apply(在 Scala REPL 中)

scala> val f = (x: Int) => x * x
f: Int => Int = $$Lambda$1020/826690115@7f8633ae

scala> f.apply
   def apply(v1: Int): Int

我收到了两个错误报告:

Seemingly using structural type

A more weird bug demo

所以说不定我也落入了这个陷阱。

顺便说一句,如果printValues()在下面,则lambda表达式可以适合方法printValues2()

def printValues2(f: (Int) => (Int), from: Int, to: Int) {
  for(i <- from to `to`) print(f(i) + " ")
  print("\n")
}

感谢您分享您的想法,祝您好运。

【问题讨论】:

  • printValues((x: Int) => x * x, 3, 6) 在 REPL 和 IDEA 中为我工作
  • @Ivan 哪个 Scala 版本? OP 也应该指定它。
  • @lvan 我在 scala-2.9.1(jdk 1.7) 中运行代码,生活很美好。但是,当我回到 scala-2.12.2(jdk 1.8) 时,麻烦就来了。
  • 适用于 2.11.11
  • Scala 问题跟踪器中的错误:github.com/scala/bug/issues/10334 和待定修复 github.com/scala/scala/pull/5977,可能会成为 2.12.3 的一部分。

标签: scala lambda nosuchmethoderror structural-typing


【解决方案1】:

只是对这个错误和解决方法的部分解释。

结构类型使用反射,所以每次你从结构类型调用一个方法(比如你的例子中的f(i))你都在做类似f.getClass.getMethod.invoke(i)的事情(所以方法解析发生在运行时)。让我们简化您的代码(2.12.2 REPL):

scala> val f: {def apply(n: Int):Int} = (x: Int) => x * x
f: AnyRef{def apply(n: Int): Int} = $$Lambda$1250/571435580@5eb041b5

scala> f(4)
java.lang.NoSuchMethodException: $$Lambda$1250/571435580.apply(int)
  at java.lang.Class.getMethod(Class.java:1786)
  at .reflMethod$Method1(<console>:13)
  ... 29 elided

并反省它以查看 f 在运行时实际上具有哪些方法

scala> f.getClass.getMethods
res21: Array[java.lang.reflect.Method] = Array(public int $$Lambda$1250/571435580.apply$mcII$sp(int), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long)

scala> f.getClass.getMethods()(0)
res23: java.lang.reflect.Method = public int $$Lambda$1250/571435580.apply$mcII$sp(int)

在这里您可以很容易地看到 scala(粗略地说)将您的 Int =&gt; Int lambda 编译为 apply$mcII$sp 方法,但是反射调用正在寻找 apply(int) 方法,该方法由于某种原因未添加到最终字节码中。

这里是解决方法:

scala> val m = f.getClass.getMethods()(0)
m: java.lang.reflect.Method = public int $$Lambda$1250/571435580.apply$mcII$sp(int)

scala> m.setAccessible(true)

scala> m.invoke(f, new Integer(5))
res50: Object = 25

如果f(i) 抛出异常,您可能必须这样做作为备份。

当然,需要Int =&gt; Int 会快得多:)

【讨论】:

    【解决方案2】:

    似乎是 Scala 2.12 编译器 中的 Scala 编译器 错误不会生成 指定类型 方法:

      val t1  = (x: String) => x + x
      t1.getClass.getMethods.foreach(println)
    

    ...

    公共 java.lang.Object TestFooBar$$$Lambda$6/1265210847.apply(java.lang.Object) public final void java.lang.Object.wait(long,int) 抛出 java.lang.InterruptedException

    在上面的代码中,从高阶函数f1获取所有方法,发现只有Object ... apply(java.lang.Object)方法,没有String ... apply(java.lang.String)方法,所以当传递给structural type:f: {def apply(n: String):String},它会抛出NoSuchMethodException

      val t2 = new Function1[String, String] {
        def apply(x: String): String = x + x
      }
    

    ...

    public java.lang.String TestFooBar$$anon$1.apply(java.lang.String) public java.lang.Object TestFooBar$$anon$1.apply(java.lang.Object)

    ...

    在上面的代码中,我们为此使用了匿名类 Function1。本质上是高阶函数(x: String) =&gt; x + x 等于new Function1[String, String]。但我们可以发现匿名类生成了一个指定类型方法:String ... apply(java.lang.String)

    所以这可能是 Scala 2.12 编译器问题,没有为高阶函数生成指定类型方法。

    为什么在这个测试中使用 String 而不是 Int

    由于Function1@specialized 原始类型(Int),需要避免这种影响。

    【讨论】:

      猜你喜欢
      • 2014-05-22
      • 2021-09-01
      • 2011-02-13
      • 1970-01-01
      • 2019-02-02
      • 1970-01-01
      • 2019-09-24
      • 2011-05-29
      • 1970-01-01
      相关资源
      最近更新 更多