无法在泛型类型的类中将Double转换为Float

3

我尝试在Scala中使用泛型类型,但遇到了困难。以下是我的代码片段。

 import scala.reflect.ClassTag

 class test[A:ClassTag] {
  private val arr: Array[A] = Array.tabulate(10){ x=>
    ((1.0 + x) / 5.0).asInstanceOf[A]
  }

  def apply(i: Int): A = arr(i)
}

val obj = new test[Float]
println(obj(1))

这段代码会抛出错误

java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Float
    at scala.runtime.BoxesRunTime.unboxToFloat(scratch_1.scala:105)
    at scala.collection.mutable.ArrayBuilder$ofFloat.$plus$eq(scratch_1.scala:460)
    at scala.Array$.tabulate(scratch_1.scala:327)
    at #worksheet#.test.<init>(scratch_1.scala:4)
    at #worksheet#.obj$lzycompute(scratch_1.scala:11)
    at #worksheet#.obj(scratch_1.scala:11)
    at #worksheet#.#worksheet#(scratch_1.scala:11)

当A的类型为Double时,这段代码输出的类型也是Double。

但是当A的类型为Float时,会抛出错误。

如果您有任何反馈,将非常有用。


小数总是“Double”,您必须明确地写出“1.0f”。 - Lino
如果我这样做,那么我将无法将Double作为我的类类型传递。它会显示java.lang.ClassCastException:java.lang.Float无法转换为java.lang.Double。 - wadhwasahil
你可以将 A 绑定到 Numeric 并调用相应的 toXXX 方法。 - László van den Hoek
你所需要的是一个 fromDouble 方法,但它并不在 Numeric(或 Fractional)中。 - Alexey Romanov
3个回答

2

您可以通过在 A: ClassTag 类型参数中添加 @specialized(Float) 来解决此问题。

这是一个稍微简化了的代码版本,其中添加了 @specialized(Float) 标签。(我还添加了 @specialized(Int) 以使示例更完整。)

import scala.reflect.ClassTag

class Test[@specialized(Float, Int) A: ClassTag] {
  def apply(count: Int): Array[A] = Array.tabulate(count) { 
    i => (1.5 + i).asInstanceOf[A];
  }
}

val floatTest = new Test[Float];
println(floatTest(5).mkString(", ")); // 1.5, 2.5, 3.5, 4.5, 5.5
// Works because code is specialized for float
// and primitive double is cast to primitive float.

val doubleTest = new Test[Double];
println(doubleTest(5).mkString(", ")); // 1.5, 2.5, 3.5, 4.5, 5.5
// Works although code is not specialized for double
// but Double object can be cast to Double object.

val intTest = new Test[Int];
println(intTest(5).mkString(", ")); // 1, 2, 3, 4, 5
// Works because code is specialized for int
// and primitive double is cast to primitive int.

val longTest = new Test[Long];
println(longTest(5).mkString(", ")); // ClassCastException
// Fails because code is not specialized for long
// and Double object cannot be cast to Long object.
// Works if you add @specialized(Long).

将Double对象转换为Double对象的无操作转换称为标识转换

您还可以省略@specialized中的类型列表。在这种情况下,该类将针对所有原始类型进行专门化:

class Test[@specialized A: ClassTag] {
...

请注意,这将生成更多的类文件。例如,如果所有Scala集合类都针对所有原始类型进行了专门化处理,Scala语言库的大小会突然增加约十倍
根本问题在于:装箱原始类型的强制转换规则与未装箱原始类型的强制转换规则非常不同。
在这种情况下(使用Java语法):
double d = 1.0 / 5.0; // 0.2 as a double primitive value
float f = (float)d; // 0.2f as a float primitive value

正常工作。将double转换为float称为缩小原始转换

但是这个不起作用:

java.lang.Double d = 1.0 / 5.0; // 0.2, but in a java.lang.Double object
java.lang.Float f = (java.lang.Float)d; // ClassCastException

由于java.lang.Doublejava.lang.Float都不是彼此的子类,因此无法进行缩小或扩大转换,也不会应用装箱和拆箱转换。更准确地说,在第一行中有一个从doublejava.lang.Double的隐式装箱转换,但在第二行中没有应用任何隐式装箱或拆箱转换。

Scala使问题变得更加混乱,因为它试图隐藏这种区别-Scala只有类型Double,而没有类型double。根据上下文,Scala类型Double有时(我认为大多数情况下)被映射到Java原始类型double,有时被映射到Java引用类型java.lang.Double


1
这实际上是几个泄漏的抽象交互。当 A 恰好是 Float 时,.asInstanceOf[A] 并不变成 .asInstanceOf[Float];它实际上是 .asInstanceOf[Object],在这种情况下会给你一个 java.lang.Double。必须这样做,因为只有一个 class test,没有不同的 A 版本,并且 .asInstanceOf[Object] 是与 JVM 字节码中的 .asInstanceOf[Float] 完全不同的操作。
将其强制转换为 Float 是稍后添加的,在您的代码中不可见,然后 java.lang.Double 无法转换为它(与 Double 不同!)。
我认为最合理的方法是定义一个 typeclass(评论中提到了 Numeric,但它没有我们想要的方法)。
trait FromDouble[A] {
  def apply(x: Double): A
}
object FromDouble {
  implicit object DoubleFromDouble extends FromDouble[Double] {
    def apply(x: Double) = x
  }
  implicit object FloatFromDouble extends FromDouble[Float] {
    def apply(x: Double) = x.toFloat
  }

  // optional, to simplify test.arr a bit
  def apply[A](x: Double)(implicit fromDouble: FromDouble[A]) = fromDouble(x)
}

class test[A : ClassTag : FromDouble] {
  private val arr: Array[A] = Array.tabulate(10){ x=>
    FromDouble[A]((1.0 + x) / 5.0)
  }

  def apply(i: Int): A = arr(i)
}

-1

虽然这个链接可能会提供一些有限的即时帮助,但是一个答案应该包括足够的上下文信息来解释这个链接,让你的同行用户知道它是什么以及为什么在那里。始终引用重要链接中最相关的部分,以使其对未来读者具有其他类似问题更有用。此外,其他用户往往会对几乎只是指向外部网站的答案做出负面反应,并且它们可能会被删除。 - Lino
它会给出与我之前评论的相同错误 java.lang.ClassCastException: java.lang.Float无法转换为java.lang.Double。此外,我事先不知道类型是浮点数还是双精度。 - wadhwasahil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接