2015-11-01 2 views
0

Следующий код компилируется нормально, но сбой во время выполнения (протестирован с помощью scala 2.9.2).Scala: как List [Double] отличается от List [Int], который вызывается в List [Double]?

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 versus Double) или «ковариации» (List [Int] как List [Double]), но я не понимаю.


Вот индивидуальный выход в Scala РЕПЛ:

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

Виды Int и Double обе идут как их тип объекта во время выполнения, когда вы пытаетесь использовать метод на них. Помимо этого, все равно, как это происходит на 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 с, с Int значениями в инициализаторе, это сделать это следующим образом:

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

Это вызывает apply метод универсального типа Double, а чем от Int, и устраняет необходимость в отливке.

По этой причине компилятор не исправляет вашу проблему здесь, потому что вы делаете asInstanceOf. Это в основном говорит компилятору: «Я знаю, что я делаю, не выглядит правильно, но продолжайте и доверяйте мне». Явным образом бросая, вы говорите компилятору, чтобы он не жаловался на использование неправильного типа.

В ответ на ваши комментарии спецификация языка Scala очень мало говорит об этом типе (предположительно, потому что это не идиоматично). Я могу предположить, почему это так. Если вы посмотрите на документы для Any, вы увидите, что класс asInstanceOf is a method, а не нечто уникальное на языке, как на Java.Это означает, что он будет вести себя так же, как любой другой метод. И это подписи типа, asInstanceOf[TO]:TO, означает, что сделанный вами бросок совершенно безопасен для текста с точки зрения компилятора. Предположительно, это не будет отмечено компилятором, потому что его просто обрабатывают, как и любой другой метод, вместо добавления дополнительного правила (и более сложного) в язык.

Просматривая мою копию Программирование в Scala Second Edition (что довольно авторитетно, так как это написано Мартином Одерским), похоже, это поддерживается. Из раздела 15.2:

Операторы isInstanceOf и asInstanceOf рассматриваются как предопределенные методы класса Any, которые принимают параметр типа в квадратных скобках. Фактически, x.asInstanceOf [String] - это частный случай вызова метода с явным параметром типа String

Как вы уже заметили, в Scala довольно много написания тестов и произнесений. Это намеренно, потому что это не поощряется.

+0

Да Java не допускает такой бросок (даже не '' List в 'Список '), как жаловалась компилятором. Компилятор Scala * никогда не жалуется на 'asInstanceOf' (даже при литье' List [Int] 'на' String'), но * некоторые * недопустимые приведения бросают исключение во время выполнения (например, при литье 'List [Int]' в 'String'), в то время как другие продолжают выполнение (например, приведение к' List [String] 'или' List [Double] ') без ошибок. Здесь ошибка переносится на выполнение «карты», потому что «Двойной» является «только» расширенным представлением, а не суперклассом (в этом случае все будет хорошо). Эти три (!) Отдельных случая раздражают меня. –

+0

Вкратце сформулировано коротко: есть ли еще одно объяснение, которое * не * полагается на внутренние элементы Java, но только на самой спецификации языка Scala? –

+0

В ответ на ваше редактирование: я просто понял, что 'val l3 = List (1, 2, 3)' даже дает ошибку времени компиляции * при попытке «l3 map fun», как и должно быть! Поэтому я вижу теперь, что в основе всей этой проблемы лежит «asInstanceOf», как указано вами. Поэтому я, вероятно, должен избегать 'asInstanceOf', когда это возможно (и использовать его только в коллекциях, если я * absoluetly * уверен, что все элементы будут переданы в супертип *, а не только в другое представление) –

0

Проблема здесь в том, что не существует неявное преобразование из Scala Int в Double, так что линия, которая выходит из строя один перед вашей линии. На самом деле он не падает, но линии не делают того, что он должен.

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

Существует неявное преобразование из' Int' to 'Double'; расширяющееся преобразование, то же, что и [в Java] (https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html). Он мог бы сделать «fun (1)» или «val myInt: Int = 1; fun (myInt) 'без проблем. – resueman

+0

Действительно, листинг 'List [Subtype]' to 'List [Supertype]' позволяет без проблем запускать функцию 'fun (x: Supertype)' на 'map'. Но листинг 'List [Subview]' to 'List [Superview]' does * not * разрешает применение 'fun (x: Superview)'. –

+0

Вкратце: странно, что 'fun (myInt)' отлично работает, но 'map (myInt => fun (myInt))' терпит неудачу. –

Смежные вопросы