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

时间:2018-07-26 11:21:09

标签: java scala

尝试在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 类型时,它将引发此错误。

这里的任何反馈都是非常有用的。

3 个答案:

答案 0 :(得分:2)

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

以下是代码的简化版本,其中添加了@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对象的无操作转换称为identity conversion。)

您还可以从@specialized中省略类型列表。在这种情况下,该类将专用于所有原始类型:

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

请注意,尽管这样会生成更多的类文件。例如,如果所有Scala集合类将专用于所有原始类型,则the Scala language library would suddenly become about ten times as large


根本问题是:装箱的图元的转换规则与未装箱的图元的转换规则非常不同。

在这种情况下(使用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

按预期工作。从doublefloat的转换称为narrowing primitive conversion

但这不起作用:

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 :(得分:0)

这实际上是一些相互影响的泄漏抽象。当.asInstanceOf[A]恰好是.asInstanceOf[Float]时,A不会变成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)
}

答案 2 :(得分:-1)