Scala:List [Double]与List [Double]的List [Int]有什么不同?

时间:2015-11-01 11:59:57

标签: scala

以下代码编译良好但在运行时崩溃(使用scala 2.9.2测试)。

object Test {

    def fun(x:Double) : Double = { 1.234 * x }

    def main(args: Array[String]) {
        val l1 = List(1.0, 2.0, 3.0)
        val lfun1 = l1 map fun
        println(lfun1)

        val l2 = List(1, 2, 3).asInstanceOf[List[Double]]
        val lfun2 = l2 map fun // <--- crashes
        println(lfun2)
    }
}

输出:

List(1.234, 2.468, 3.702)
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
    at scala.runtime.BoxesRunTime.unboxToDouble(BoxesRunTime.java:114)
    at Test$$anonfun$2.apply(Covariance.scala:11)
    at scala.collection.immutable.List.map(List.scala:273)
    at Test$.main(Covariance.scala:11)
    at Test.main(Covariance.scala)

有关详细信息,请参阅下面的REPL输出。

我来自Java并想学习Scala,所以有人可以向我解释它崩溃的原因以及为什么编译器无法检测到这一点?我想这与“视图”(Int与Double)或“covariance”(List [Int]为List [Double])有关,但我不明白这一点。

以下是Scala REPL中的单个输出:

scala> def fun(x:Double) : Double = { 1.234 * x }
fun: (x: Double)Double

scala> val l1 = List(1.0, 2.0, 3.0)
l1: List[Double] = List(1.0, 2.0, 3.0)

scala> val lfun1 = l1 map fun
lfun1: List[Double] = List(1.234, 2.468, 3.702)

scala> println(lfun1)
List(1.234, 2.468, 3.702)

scala> val l2 = List(1, 2, 3).asInstanceOf[List[Double]]
l2: List[Double] = List(1, 2, 3)

scala> val lfun2 = l2 map fun
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Double
    at scala.runtime.BoxesRunTime.unboxToDouble(Unknown Source)
    at $anonfun$1.apply(<console>:9)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:233)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:233)
    at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)
    at scala.collection.immutable.List.foreach(List.scala:76)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:233)
    at scala.collection.immutable.List.map(List.scala:76)
    at .<init>(<console>:9)
    at .<clinit>(<console>)
    at .<init>(<console>:11)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
    at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
    at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
    at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
    at java.lang.Thread.run(Thread.java:745)

2 个答案:

答案 0 :(得分:2)

崩溃的原因是

  

java.lang.Integer无法强制转换为java.lang.Double

只要您尝试对它们使用方法,类型IntDouble都将在运行时成为它们的Object类型。除此之外,它与Java中的情况完全相同。你要做的是类似的事情:

List<Double> l2 = ((List<Double>)new ArrayList<Integer>());

因为List(1, 2, 3)被推断为List[Int],然后您将其转换为List[Double]。而且,由于Integer不是Double,它会崩溃。当您尝试在map中使用它时,会发生实际问题。当您尝试进行调用时,或者当它在方法中进行计算时,Java unboxing rules将被应用,这与此类似:

((Double)actuallyAnInteger).doubleValue();

导致ClassCastException

相反,在初始值设定项中创建List Double IntList[Double] l2 = List[Double](1, 2, 3) 值时,您想要做的是这样做:

apply

调用泛型类型Double的{​​{1}}方法,而不是Int,并且不需要强制转换。

至于编译器在这里没有解决你的问题的原因,那是因为你做asInstanceOf。这基本上是对编译器说“我知道我在做什么看起来不对,但继续相信我。”通过显式转换,您告诉编译器不要抱怨使用不正确的类型。

在回应您的评论时,Scala语言规范对此类演员几乎没有什么可说的(大概是因为它不是惯用语)。我可以猜想为什么会这样。如果您查看Any的文档,您会看到该类的asInstanceOf is a method,而不是像Java中那样的语言中的独特内容。这意味着它的行为与任何其他方法一样。它的类型签名asInstanceOf[TO]:TO意味着从编译器的角度来看,你正在进行的转换是完全类型安全的。据推测,这并没有被编译器标记,因为它只是被视为任何其他方法,而不是在语言中添加额外的规则(和更多的复杂性)。

查看我在Scala Second Edition中的编程的副本(由于它是由Martin Odersky编写的,因此非常权威),这似乎得到了支持。从第15.2节开始:

  

运算符isInstanceOf和asInstanceOf被视为类Any的预定义方法,它在方括号中采用类型参数。实际上,x.asInstanceOf [String]是带有显式类型参数String

的方法调用的特例。      

正如您现在已经注意到的那样,在Scala中编写类型测试和强制转换是相当冗长的。这是故意的,因为不鼓励练习。

答案 1 :(得分:0)

这里的问题是,不存在从Scala IntDouble的隐式转换,因此崩溃的行是您的行之前的行。真的不会崩溃,但这些线条并没有达到应有的水平。

Yous应该像这样工作:

object StackSample extends App {

  def fun(x:Double) : Double = { 1.234 * x }

  val l1 = List(1.0, 2.0, 3.0)
  val lfun1 = l1 map fun
  println(lfun1)

  //val l2 = List(1, 2, 3).asInstanceOf[List[]]// <--- crashes

  val l2: List[Double] = List(1,2,3) map (_.toDouble)

  val lfun2 = l2 map fun
  println(lfun2)
}