2015-11-01 44 views
0

以下代碼在運行時編譯正常但崩潰(用scala 2.9.2測試)。Scala:List [Double]與列表[Double]的列表[Int]有什麼不同?

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)或「協方差」(列表[Int]爲列表[Double])有關,但我沒有理解。


這裏是斯卡拉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

的原因崩潰是

java.lang.Integer中不能轉換爲java.lang.Double中

類型IntDouble都會當你試圖在它們上使用一個方法時,它們就是它們的對象類型。除此之外,它在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

相反,你想要做的創建DoubleList一個S,在初始化Int值是什麼,是做這樣的:

List[Double] l2 = List[Double](1, 2, 3) 

這就要求泛型類型Doubleapply方法,而比Int,並且不需要演員。

至於編譯器不能糾正你的問題的原因,這是因爲你做asInstanceOf。這基本上是對編譯器說:「我知道我在做什麼看起來不正確,但請繼續並相信我。」通過明確的轉換,你告訴編譯器不要抱怨使用不正確的類型。

作爲對您的評論的迴應,Scala語言規範對這種類型的演員(很可能是因爲它不是慣用的)很少有評論。我可以猜想爲什麼它是這種方式。如果您查看Any的文檔,您會看到該類的asInstanceOf is a method,而不是像Java中那樣的語言中唯一的東西。這意味着它將表現得非常像其他任何方法。它是類型簽名,asInstanceOf[TO]:TO,意味着你正在做的演員從編譯器的角度來說是完全類型安全的。據推測,這不會被編譯器標記,因爲它只是像其他任何方法一樣對待,而不是在語言中添加額外的規則(以及更多的複雜性)。

翻閱我的副本編程斯卡拉第二版(這是相當權威的,因爲它是由馬丁Odersky寫的),這似乎是支持。從第15.2節開始:

運算符isInstanceOf和asInstanceOf被視爲類的預定義方法任何將方括號中的類型參數。實際上,x.asInstanceOf [String]是一個具有顯式類型參數的方法調用的特例。字符串

正如您現在已經注意到的那樣,在Scala中編寫類型測試和強制轉換相當詳細。這是故意的,因爲它不鼓勵練習。

+0

是Java允許的抱怨編譯器沒有這樣的投(甚至沒有'名單''到列表')。 Scala編譯器*從不*抱怨'asInstanceOf'(甚至在將'List [Int]'強制轉換爲'String'時也不會),但是*一些*無效強制轉換會在運行時拋出異常(例如,當將'List [Int]'強制轉換爲'String'),而其他的則繼續執行(例如強制轉換爲'List [String]'或'List [Double]')而沒有錯誤。在這裏,錯誤被推遲到執行'map',因爲'Double'只是「擴展視圖」,而不是超類(在這種情況下,所有情況都可以)。這三種(!)不同的情況會刺激我。 –

+0

總而言之:有沒有另外一種解釋不依賴於Java內部,而只依賴於Scala語言規範本身? –

+0

爲了響應你的編輯:我剛剛意識到'val l3 = List(1,2,3)'甚至在嘗試'l3 map fun'時會產生*編譯時錯誤*,就像它應該那樣!所以我現在看到,正如你所指出的那樣,所有這些麻煩的核心在於「asInstanceOf」。所以我可能應該儘可能避免使用'asInstanceOf'(並且只有在我*絕對*確保所有元素都被轉換爲*超類型時,纔會在另一個視圖中使用它) –

0

這裏的問題是,不從斯卡拉Int存在的隱式轉換到Double,使崩潰的線是一個你前行。真的它不會崩潰,但線條並沒有達到預期的效果。

你要跟應該像這樣工作:

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) 
} 
+0

確實存在從' Int'到'Double';擴展轉換,與[在Java中](https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html)相同。他可以做'有趣(1)'或'val myInt:Int = 1; fun(myInt)'沒有任何問題。 – resueman

+0

事實上,將'List [Subtype]'強制轉換爲'List [Supertype]'允許在'map'處應用函數fun(x:Supertype)',而不會出現任何問題。但是'List [Subview]'List'Superview]'不允許應用'fun(x:Superview)'。 –

+0

簡而言之:'fun(myInt)'工作正常,但'map(myInt => fun(myInt))'失敗。 –

相關問題