2

Trying to cast a String to a Double obviously should fail:

scala> Try("abc".asInstanceOf[Double])
res11: scala.util.Try[Double] = Failure(java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double)

However, if I define the above as a function:

scala> def convertAsTypeOf[T](anyValue: Any): Try[T] = Try(anyValue.asInstanceOf[T])
convertAsTypeOf: [T](anyValue: Any)scala.util.Try[T]

Strangely it returns Success when I try to cast a String to a Double:

scala> convertAsTypeOf[Double]("abc")
res10: scala.util.Try[Double] = Success(abc)

If I try to get the value out of Success, I get the following exception:

scala> convertAsTypeOf[Double]("abc").get
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
        at scala.runtime.BoxesRunTime.unboxToDouble(BoxesRunTime.java:119)

Why does this happen? Is there any way to fix this and make asInstanceOf[T] when it's in a generic function?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Xzer
  • 93
  • 6

2 Answers2

4

Because of type erasure, the type T is unknown at runtime, so asInstanceOf can't actually check the cast that it does. In fact, it compiles as a no-op. However, when the .get is finally done a cast is made to Double, and because the type is known here we can get a ClassCastException.

If you change convertAsTypeOf to use ClassTag:

def convertAsTypeOf[T](anyValue: Any)(implicit tag: ClassTag[T]): Try[T] =
  Try(tag.runtimeClass.cast(anyValue).asInstanceOf[T])

You will then get errors when you expect:

scala> convertAsTypeOf[Double]("abc")
res1: scala.util.Try[Double] = Failure(java.lang.ClassCastException: Cannot cast java.lang.String to double)

The ClassTag represents the type T at runtime, allowing the value to be checked against it.

wingedsubmariner
  • 13,350
  • 1
  • 27
  • 52
  • Hi, thanks for your answer. It does not work on Double or Int argument. `scala> convertAsTypeOf[Double](32.2) res86: scala.util.Try[Double] = Failure(java.lang.ClassCastException: Cannot cast java.lang.Double to double)` – Xzer Aug 11 '14 at 02:52
1

The reason this happens is type erasure, which makes asInstanceOf[T] for generic T useless. I don't know why the compiler doesn't issue any warning.

You can force the check to happen by using a ClassTag. Something like this:

import reflect.ClassTag

def convertAsTypeOf[T : ClassTag](anyValue: Any): Try[T] = Try {
    anyValue match { case t: T => t }
}

This works because the compiler will use the implicit ClassTag in the case match if it is available (and generate a warning if it is not).

Joe Pallas
  • 2,105
  • 1
  • 14
  • 17
  • 2
    `asInstanceOf[T]` lets you regain type safety when you know something the compiler doesn't. – som-snytt Aug 10 '14 at 05:48
  • If `T` is erased, won’t type safety require a run-time check somewhere? How does the compiler decide where to put that check? – Joe Pallas Aug 11 '14 at 15:24
  • It happens when you use the value as some type. In REPL, compare `val x = identity(2.0)` and `val x: Any = identity(2.0)` using `:javap -prv -`. Or `f[A](a: A) = 42.asInstanceOf[A]`, it will throw in the first case. – som-snytt Aug 11 '14 at 18:49