【问题标题】:Unwanted conversion from Scala AnyVal types to Java Types从 Scala AnyVal 类型到 Java 类型的不需要的转换
【发布时间】:2019-12-03 15:18:51
【问题描述】:

我正在编写一个函数来获取一些参数并提取它们的类型,但是只要这些参数在 Scala AnyVal (Int, Long, Double, ...) 下,它们就会转换为 Java 类型 (Integer, ...) .

这里是那个函数的一个小例子:

def functionX(args: Any*) = {
   args.map(a => a.getClass)
}

val num: Int = 10

println(num1.getClass) // prints int

println(functionX(num1)) // prints Integer

我应该怎么做才能让所有参数类型(无论它们是 Int 还是 Integer)都保持原始类型并且不进行转换?

我还意识到,如果将变量放入 Scala 集合中,它也会被转换为 Java 类型:

val num: Int = 1
val numList = List(num)

println(num.getClass) // prints int
println(numList.head.getClass) // prints Integer

是否有任何 Java 自动装箱发生?但是,Scala 类型(如 Int)不是在 AnyVal 类下,也不是原始类型?


为了提供更多关于我为什么要这样做的背景信息,这里有一个例子:

trait MyTrait {
  def myTraitMethod : String
}

object MyObj extends MyTrait {
  def myTraitMethod = "Works fine"
  def myObjMethod(num1: Int, num2: Int) = num1 * num2
}

case object Manager {
  def currentObj: MyTrait = MyObj
}

val result = Manager.currentObj.myObjMethod
// I wanted to access this object method that is not in Trait, but
// it doesn't seem possible.

我没有做上面代码的最后一行,而是使用反射来做这样的事情:

def executeMethod(obj: MyTrait, methodName: String, args: Any*) = {
    val argTypes = args.map(t => t.getClass)
    val argsObjects = args.map(o => o.asInstanceOf[Object])

    val method = obj.getClass.getMethod(methodName, argTypes:_*)

    method.invoke(obj, argsObjects:_*)

}

val num = 10
val result = executeMethod(Manager.currentObj, "myObjMethod", num1, num2)
// That's where I have problem with Int being converted to Integer
// and the method is not found because it's (Int, Int) and not (Integer, Integer)

【问题讨论】:

  • 编译时 Scala 类型和运行时 Java 类是不同的东西。在语言级别上,我们只有对象 (例如 Int,但运行时世界是另一回事,在 JVM 上运行时(记住 Scala 可能运行在其他平台,如 JS) 我们有对象和原语,但你不能要求原语的类,因此应用了自动装箱。你究竟为什么要这样做?
  • 如果需要AnyVal,为什么还要使用Any
  • Any 通常是设计问题的症状
  • @HissashiRocha 如果这只是为了单元测试,那么对于这种情况有两种一般的想法。一个是你不应该测试私有行为的想法,因此你应该只测试公共 API (它会传递测试私有方法) - 另一方面,有一个论点是这样做正确的单元测试,你也应该测试这些方法,通常的解决方法是让它们private[package],这样你的测试就可以在不需要反射的情况下访问它们。
  • @HissashiRocha 试试这个:Manager.currentObj.asInstanceOf[MyObj.type].myObjMethod(2, 3)

标签: java scala


【解决方案1】:

functionX 是一个函数,它接受可变数量的 Any 类型的参数。

在某些时候,Scala 将不得不将其编译成可以在 JVM 上运行的函数。该函数将采用一个参数,它将是数组或序列类型,因为这就是 JVM 实现可变参数的方式。事实上,它将是scala.collection.immutable.ArraySeq

您可能了解 JVM 基本类型不是对象。您不能有一个变量来保存像int 这样的原始值String 这样的对象。 JVM 包括“盒子”类型IntegerDouble 等,因此您可以在必要时使用原始值作为对象。

ArraySeq 必须是某物的集合,因为在functionX 的情况下,您已经说过它可以容纳任何东西,在JVM 级别,某物必须是Object。由于它是 ObjectArraySeq,因此您要放入其中的任何原始值都必须被装箱。

一旦您意识到 Scala 的 Int 类型是 JVM 的原始 int 类型,您观察到的所有行为都会变得有意义。它们并不是真正的对象。

为了通过反射调用正确的方法,您可以假设如果参数的类型为Integer,那么您应该使用Integer.TYPEclassOf[int] 类型来查找方法。当然,如果您想要的方法实际上采用Integer,那将失败。找到正确的方法后,JVM 将负责对参数列表中的任何装箱值进行拆箱。

【讨论】:

  • 现在很有意义。感谢分享这个!我会想一个替代方案。
猜你喜欢
  • 2014-09-26
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 2013-11-15
  • 2013-06-28
  • 1970-01-01
  • 2020-03-07
  • 2018-10-31
相关资源
最近更新 更多