【问题标题】:Getting java.lang.NoSuchMethodException in structural typing of AnyVal在 AnyVal 的结构类型中获取 java.lang.NoSuchMethodException
【发布时间】:2020-08-07 00:43:37
【问题描述】:

我有以下 sn-p,它(我认为)在泛型类型 T 上定义了一个方法 addNumber1(x:T):T,它是 AnyVal 的子类型,并且有一个方法 +(s:Int):T

def addNumber1[T <: AnyVal {def +(s:Int):T}](x:T):T = {x + 1}

addNumber1(31) // compiles but throws exception

java.lang.NoSuchMethodException: java.lang.Integer.$plus(int)
  at java.lang.Class.getMethod(Class.java:1786)
  at .reflMethod$Method1(<console>:8)
  at .addNumber1(<console>:8)
  ... 33 elided

我尝试添加 import scala.language.reflectiveCalls 来抑制功能警告,但仍然出现错误。

我可以在使用AnyRefAny 时使用它,如下所示:

def addNumber1[T <: Any {def +(s:Int):T}](x:T):T = {x + 1}

class Foo(s:String) {def +(i:Int) = new Foo((s+1).toString)} // random code
class Bar(s:Foo) {def +(i:Int) = new Bar(new Foo(i.toString))} // random code

addNumber1(new Foo("1"))  // works
addNumber1(new Bar(new Foo("1")))  // works
addNumber1(1) // compiles but gives exception

【问题讨论】:

    标签: scala structural-typing


    【解决方案1】:

    我认为问题与 AnyVal、AnyRef 或 Any 无关。

    addNumber1(new Foo("1")) 
    

    这是有效的,因为您确实定义了一个 Foo 类,它提供了 def +(s:Int):T 的实现。

    addNumber1(1)
    

    这不起作用,因为 Integer 类不提供它,如异常中所述:

    java.lang.NoSuchMethodException: java.lang.Integer.$plus(int)
    

    【讨论】:

    • 问题是它编译的原因。
    • 它编译是因为 scala 编译器“认为” Int 确实有 + 运算符,它必须这样做,因为例如 i + 1 在 i 的类型为 Int 时是有效的。
    • 我使用 AnyVal 是因为它们映射到原始 Java 类型。仅在使用原始 Java 类型时才会出现此问题。
    • addNumber1(31) 中的数字 31 不是 Java 原始类型,它是 Scala 原始类型 Int,这是问题的根源,Scala 编译器认为 Int 有一个 + 运算符在编译时,这是真的,但一旦在运行时将 Int 装箱为 Integer,则为假。
    • 这就是我的意思。 “使用(映射到的 Scala 类型)Java 基元类型时会出现问题。”等效地,我可以写“问题只发生在 AnyVals”,这是问题的标题。也许编译器实际上可以推断它是否是结构类型(和其他地方)中使用的“魔术”方法。
    【解决方案2】:

    你遇到了很多功能的交集:

    1. 就 Scala 编译器的初始阶段(包括类型检查)而言,Int 确实有一个(重载的)+ 方法。但是这个“方法”在后期被特殊对待(就像Int上的所有方法一样,因为它不是一个真正的类)。

    2. 在 Scala 中定义的名为 + 的方法被转换为字节码中名为 $plus 的方法,因为 + 在此处不是合法标识符。由于Int 上的+ 是特殊的,如上所述,这不适用于它。由于结构类型是使用 Java 反射实现的,因此您的 addNumber1 看起来有点像

      def addNumber1(x: Object) = x.getClass.getMethod("$plus").invoke(x, 1)
      
    3. 要在int 上调用addNumber1,必须首先将其装箱到Integer,因为int 不是对象。 Integer,不是 Scala 类型,没有 $plus 方法。在 Scala 中,您可以编写类似 val x: Integer = ...; x + 1 的内容,但这使用了 Java 反射不知道的隐式转换。

    【讨论】:

    • 我认为结构类型应该使用Scala反射来实现,它已经变得相当稳定了。
    猜你喜欢
    • 2019-09-10
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-17
    • 2017-05-02
    相关资源
    最近更新 更多