scala generics classcastexception scala-option scala-generics

scala - ¿Por qué envolver una llamada a un método genérico con la opción difiere la excepción ClassCastException?



generics scala-option (2)

Digamos que tengo una matriz como esta *:

val foo: Any = 1 : Int Option(foo.asInstanceOf[String])

que falla por razones obvias:

// java.lang.ClassCastException: java.lang.Integer cannot be cast to // java.lang.String // ... 48 elided

A continuación consideremos una clase siguiente:

case class DummyRow() { val foo: Any = 1 : Int def getAs[T] = foo.asInstanceOf[T] def getAsOption[T] = Option(foo.asInstanceOf[T]) }

Por lo que sé, getAs debe comportarse de la misma manera que la apply anterior, seguido de asInstanceOf .

Sorprendentemente no es el caso. Cuando se llama solo lanza una excepción:

DummyRow().getAs[String] // java.lang.ClassCastException: java.lang.Integer cannot be cast to // java.lang.String // ... 48 elided

pero cuando se envuelve con la Option tiene éxito:

val stringOption = Option(DummyRow().getAs[String]) // Option[String] = Some(1) DummyRow().getAsOption[String] // Option[String] = Some(1)

y falla solo cuando intento acceder al valor envuelto:

stringOption.get // java.lang.ClassCastException: java.lang.Integer cannot be cast to // java.lang.String // ... 48 elided

Entonces, ¿qué pasa aquí? Parece ser una ClassCastException limitada, así que supongo que está relacionada con una cosa fea como el borrado de tipo.

* Any y asInstanceOf están ahí para imitar un comportamiento del código de un tercero, así que, por favor, no nos detengamos en eso.

** Probado en Scala 2.10.5, 2.11.7

*** Si está interesado en el contexto, puede echar un vistazo a Uso de contiene en scala - excepción

**** Otras preguntas relevantes vinculadas en los comentarios:


A continuación se muestra una versión simplificada de su problema con un caso adicional para Any

def getAs[T] = (1:Int).asInstanceOf[T] //blows up getAs[String] //blows up def p(s:String): Unit = {} p(getAs[String]) //works def p[T](s:T): Unit = {} p(getAs[String]) //works def p(s:Any): Unit = {} p(getAs[String])

Debido a que crea un método con un parámetro genérico, el tiempo de ejecución no necesita "tocar" el valor porque no le importa. Los genéricos se tratarán como Any / Object en tiempo de ejecución.


Eche un vistazo a la siguiente sesión REPL (ligeramente editada para fines de lectura):

scala> class Foo(foo: Any) { | def getAs[T] = foo.asInstanceOf[T] | def getAsString = foo.asInstanceOf[String] | } defined class Foo scala> :javap Foo Size 815 bytes MD5 checksum 6d77ff638c5719ca1cf996be4dbead62 Compiled from "<console>" public class Foo { public <T extends java/lang/Object> T getAs(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #11 // Field foo:Ljava/lang/Object; 4: areturn LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LFoo; LineNumberTable: line 12: 0 Signature: #35 // <T:Ljava/lang/Object;>()TT; public java.lang.String getAsString(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #11 // Field foo:Ljava/lang/Object; 4: checkcast #17 // class java/lang/String 7: areturn LocalVariableTable: Start Length Slot Name Signature 0 8 0 this LFoo; LineNumberTable: line 13: 0 }

Puede ver en el getAsString de getAsString de getAsString que se checkcast una instrucción de getAsString cuando se checkcast en un String . Por otro lado, en getAs[T] no se ejecuta tal instrucción aunque haya una conversión en el código. La razón de esto es que T se borra a Any en el tiempo de ejecución, por lo que simplemente se convertiría en un elenco a Any (que nunca fallaría). De modo que la conversión a un parámetro de tipo solo es necesaria para el compilador, no para las JVM. Por lo tanto, no se debe realizar ningún lanzamiento cuando se ajusta esa llamada en Option que también es genérica. Solo cuando se desea obtener el valor de la Option y tratarlo como una String se ejecuta una conversión y se lanza una excepción.

scala> class Bar() { | def getString: String = new Foo(3).getAs[String] | def get[T]: T = new Foo(3).getAs[T] | } defined class Bar scala> :javap Bar Size 1005 bytes MD5 checksum 4b7bee878db4235ca9c011c6f168b4c9 Compiled from "<console>" public class Bar { public java.lang.String getString(); flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: new #9 // class Foo 3: dup 4: iconst_3 5: invokestatic #15 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 8: invokespecial #19 // Method Foo."<init>":(Ljava/lang/Object;)V 11: invokevirtual #23 // Method Foo.getAs:()Ljava/lang/Object; 14: checkcast #25 // class java/lang/String 17: areturn LocalVariableTable: Start Length Slot Name Signature 0 18 0 this LBar; LineNumberTable: line 13: 0 public <T extends java/lang/Object> T get(); flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: new #9 // class Foo 3: dup 4: iconst_3 5: invokestatic #15 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 8: invokespecial #19 // Method Foo."<init>":(Ljava/lang/Object;)V 11: invokevirtual #23 // Method Foo.getAs:()Ljava/lang/Object; 14: areturn LocalVariableTable: Start Length Slot Name Signature 0 15 0 this LBar; LineNumberTable: line 14: 0 Signature: #51 // <T:Ljava/lang/Object;>()TT; }

Como puede ver, checkcast se ejecuta después de getAs lugar de durante, y solo en un contexto no genérico.