19

Consider the following Scala code:

case class Data[T](value: Option[T]) { def get: T = try { doGet } catch { case e: Exception => throw new IllegalArgumentException } def doGet: T = value match { case Some(v) => v case None => ().asInstanceOf[T] } } Data[Unit](None).get Data[Integer](None).get // which exception is thrown here? 

[spoiler] It is a ClassCastException; who can explain why it is not caught and replaced by an IllegalArgumentException?

PS: To preempt any questions on why I would want to do this: this is a simplified version of some code that uses json4s to parse some string into an Option[T]; if the parsing fails None is returned, which is OK if T was Unit and not OK if T is some other type.

0

1 Answer 1

22

Explanation

Exception isn't thrown here:

().asInstanceOf[T] 

because this is an unchecked cast - JVM cannot verify if it is possible to cast () into T, because it has no information about T due to type erasure.

Instead, exception is thrown here

Data[Integer](None).get 

because the result of get is cast into an Integer and that is something that JVM can verify. So, ClassCastException is actually thrown outside of get.

BTW, javac always warns about unchecked casts, I don't know why scalac doesn't.

Workaround

To some extent, it is possible to work around type erasure here using ClassTag and reflection-based casting:

import scala.reflect.{ClassTag, classTag} case class Data[T: ClassTag](value: Option[T]) { def get: T = try { doGet } catch { case e: Exception => throw new IllegalArgumentException } def doGet: T = value match { case Some(v) => v case None => classTag[T].runtimeClass.asInstanceOf[Class[T]].cast(()) } } 

Hackaround

For this use case, you can inspect the ClassTag directly:

scala> case class Data[T](value: Option[T])(implicit t: ClassTag[T]) { | def get: T = value getOrElse (t match { | case ClassTag.Unit => ().asInstanceOf[T] | case _ => throw new IllegalArgumentException | }) | } defined class Data scala> Data[Unit](None) res6: Data[Unit] = Data(None) scala> .get scala> Data[Int](None).get java.lang.IllegalArgumentException 
Sign up to request clarification or add additional context in comments.

4 Comments

Aha! Erasure! That's the key.
Can the code be modified to make the get throw the exception?
Note that your ClassTag workaround would not work if you try this reflection-based casting to cast, e.g., a List[Int] to a List[String]. It will silently work and then fail later, throwing again a ClassCastException. There's a reason why runtimeClass doesn't return Class[T].
The ClassTag hackaround works well in this case, since only the Unit type needs special treatment and all other types (including parameterized ones) should cause the IllegalArgumentException. It is also possible to move the implicit ClassTag to the get definition.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.