Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `.asInstanceOf` sometimes throw, and sometimes not?

Tags:

scala

I was trying to answer this question, as I thought I knew the answer. Turns out, I did not know quite enough :/

Here is a test I have done:

class Inst[T] { 
  def is(x: Any) = scala.util.Try { as(x) }.isSuccess
  def as(x: Any): T = x.asInstanceOf[T]
}

scala> new Inst[String].is(3)
res17: Boolean = true

scala> new Inst[String].as(3)
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 ... 33 elided

What is going on here? Why does only the second call to as throw, but not the first one?

like image 689
Dima Avatar asked Dec 10 '15 14:12

Dima


2 Answers

This is because the class-cast-exception is only thrown when you do something with the value, call a method on it after the cast. In the REPL for example, you would have a toString call in the second case. Note:

new Inst[String].as(3); ()           // nothing happens
new Inst[String].as(3).toString; ()  // exception

The reason why this takes the extra step is that Inst[T] is generic with type parameter T which is erased at runtime; only when the call-site (that has a static knowledge of type T) tries to call a method on the result, the actual type check occurs.


For your follow-up question, toString is defined on any object and since T is generic you have a boxed integer (<: AnyRef) and toString and println succeed within the is method. So another example where the Try would fail is this:

class Inst[T] { 
  def foo(x: Any)(implicit ev: T <:< String) = scala.util.Try {
    ev(as(x)).reverse
  }.isSuccess
}

new Inst[String].foo(3)  // false!
like image 191
0__ Avatar answered Nov 06 '22 03:11

0__


While @0__'s answer explains why it doesn't work, here is how to make it work:

class Inst[T](implicit tag: scala.reflect.ClassTag[T]) {
  def is(x: Any) = tag.runtimeClass.isInstance(x) 
  // scala.util.Try { as(x) }.isSuccess will work as well
  def as(x: Any): T = tag.runtimeClass.cast(x).asInstanceOf[T]
}

object Main extends App {
  println(new Inst[String].is(3))
  println(new Inst[String].as(3))
}


false
java.lang.ClassCastException: Cannot cast java.lang.Integer to java.lang.String
    at java.lang.Class.cast(Class.java:3369)
...
like image 41
Alexey Romanov Avatar answered Nov 06 '22 03:11

Alexey Romanov