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.
=> is the "function arrow". It is used both in function type signatures as well as anonymous function terms. () => Unit is a shorthand for Function0[Unit] , which is the type of functions which take no arguments and return nothing useful (like void in other languages).
A classOf[T] is a value of type Class[T] . In other words, classOf[T]: Class[T] . For example: scala> val strClass = classOf[String] strClass: Class[String] = class java. lang. String scala> :t strClass Class[String]
Any has two direct subclasses: AnyVal and AnyRef . AnyVal represents value types. There are nine predefined value types and they are non-nullable: Double , Float , Long , Int , Short , Byte , Char , Unit , and Boolean . Unit is a value type which carries no meaningful information.
Everything is an Object. Scala is a pure object-oriented language in the sense that everything is an object, including numbers or functions. It differs from Java in that respect, since Java distinguishes primitive types (such as boolean and int ) from reference types.
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.
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(())
}
}
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
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