Search code examples
scalagenericsclasscastexceptionscala-optionscala-generics

Why wrapping a generic method call with Option defers ClassCastException?


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:


Solution

  • 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.