Lets say I have an array like this*:
val foo: Any = 1 : Int
Option(foo.asInstanceOf[String])
which fails for obvious reason:
// java.lang.ClassCastException: java.lang.Integer cannot be cast to
// java.lang.String
// ... 48 elided
Next lets consider a following class:
case class DummyRow() {
val foo: Any = 1 : Int
def getAs[T] = foo.asInstanceOf[T]
def getAsOption[T] = Option(foo.asInstanceOf[T])
}
As far as I can tell getAs
should behave the same way as the previous apply
followed by asInstanceOf
.
Surprisingly it is not the case. When called alone it throws an exception:
DummyRow().getAs[String]
// java.lang.ClassCastException: java.lang.Integer cannot be cast to
// java.lang.String
// ... 48 elided
but when wrapped with Option
succeeds:
val stringOption = Option(DummyRow().getAs[String])
// Option[String] = Some(1)
DummyRow().getAsOption[String]
// Option[String] = Some(1)
and fails only when I try to access wrapped value:
stringOption.get
// java.lang.ClassCastException: java.lang.Integer cannot be cast to
// java.lang.String
// ... 48 elided
So what happens here? It seems to be limited ClassCastException
so I guess it is related to some ugly thing like type erasure.
* Any
and asInstanceOf
are there to mimic a behavior of the 3rd party code so please lets not dwell on that.
** Tested in Scala 2.10.5, 2.11.7
*** If you're interested in the context you can take a look at Using contains in scala - exception
**** Other relevant questions linked in the comments:
Below is a simplified version of your problem with an additional case for 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])
Because you create a method with a generic parameter, the runtime doesn't need to "touch" the value because it does not care. Generic will be treated as Any
/Object
at runtime.
Take a look at the following (slightly edited for reading purposes) REPL session:
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
}
You can see in the bytecode of getAsString
that a checkcast
instruction is executed when casting to a String
. On the other hand in getAs[T]
no such instruction gets executed even though there is a cast in the code. The reason for that is that T
gets erased to Any
at runtime, so that would simply become a cast to Any
(which would never fail). So casting to a type parameter is only necessary for the compiler's sake, not the JVM's. So no casting has to happen when you wrap that call in Option
which is generic too. It's only when you want to get the value out of the Option
and treat it as a String
that a cast is executed and an exception is thrown.
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;
}
As you can see checkcast
is executed after getAs
instead of during, and only in a non-generic context.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With