4

Here's the contrived experiments in REPL (scala 2.11):

scala> class Foo[T] {
     |   def as(x: Any) = x.asInstanceOf[T]
     | }

defined class Foo

scala> val foo = new Foo[String]
foo: Foo[String] = Foo@65ae6ba4

scala> val x: Any = 123
x: Any = 123

scala> foo.as(x)  // expected
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  ... 33 elided

scala> val y: Any = "abc"
y: Any = abc

scala> foo.as(y)
res1: String = abc


scala> class Bar[T] {
     |   def is(x: Any) = x.isInstanceOf[T]
     | }

<console>:12: warning: abstract type T is unchecked since it is eliminated by erasure
         def is(x: Any) = x.isInstanceOf[T]
                                        ^
defined class Bar

scala> val bar = new Bar[String]
foo: Foo[String] = Foo@1753acfe

scala> val x: Any = 123
x: Any = 123

scala> bar.is(x)  // unexpected
res2: Boolean = true

scala> val y: Any = "abc"
y: Any = abc

scala> bar.is(y)
res3: Boolean = true

I know type parameter is quite limited due to type erasion, but still confused by the different behaviours between asInstanceOf and isInstanceOf here.

Wonder if someone has any insight about that? Thanks!

QRush
  • 77
  • 5
  • These two operations are very different, while isInstanceOf is a boolean parameter that return `true`/`false` (in your case always `true` because x is type `Any`), the asInstanceOf is actually trying to make casting from your variable to the type you wanted and hence the `java.lang.ClassCastException`. Actually both can get `Type` parameters, but the casting needs to be legit. – Avihoo Mamka Dec 10 '15 at 13:10
  • @AvihooMamka one could use `asInstanceOf` and see if it succeeds. Why `isInstanceOf` is not implemented this way? – n. m. could be an AI Dec 10 '15 at 13:21
  • The generated byte code calls Java's `instanceof`. Technically, `isInstanceOf` is part of Java's reflection, where it is known as `instanceof` – Avihoo Mamka Dec 10 '15 at 13:23
  • 1
    @n.m. `asInstanceOf` will always succeed, until you try to use it as the wrong type, but there is no generic way to know that at runtime. That is, each type fails in a different way. – Michael Zajac Dec 10 '15 at 13:59
  • @m-z hmm, looks fairly useless then. – n. m. could be an AI Dec 10 '15 at 14:08

1 Answers1

3

Well, you must know, that type parameters are not available at runtime, all information they carry can only be used by the compiler. Now, asInstanceOf is just a cast, it is only needed to the compiler to enforce the type compatibility, and at runtime it doesn't do anything at all: a reference is a reference, regarding of the type of underlying object.

isInstanceOf on the other hand is the opposite: compiler does not know anything about it, it's just a function call. It is executed at runtime to check whether the given object is of the expected type. But the type parameter is not available at runtime, so how would it know which type to check against? For this reason, it needs a real Class argument.

Dima
  • 39,570
  • 6
  • 44
  • 70
  • Exactly. If you compile it with `scalac -print`, `asInstanceOf[T]` call is removed, and you'll see a code: `def as(x: Object): Object = x` – Archeg Dec 10 '15 at 14:03
  • I think it actually a bit bugged: `def as(x:Any):Any = x.asInstanceOf[T]` does not throw if you call it `as(5)` even if `T` is `String` – Archeg Dec 10 '15 at 14:05
  • @Archeg not sure what you mean: I just tried this: `scala> new Foo[String].as(3) java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String ... 33 elided ` – Dima Dec 10 '15 at 14:08
  • Apparently `foo.asInstanceOf[Bar]` won't throw by itself for the reasons I explained in my answer (the call is simply removed at runtime), what happens in REPL, when you do `new Foo[String].as(3)` is that the REPL itself is trying to access the returned object to print it out, and treats it as `String` (because it knows "statically" that that's what `Foo[Sring].as` is supposed to return), and *that* throws. – Dima Dec 10 '15 at 15:08
  • I don't think it's anything to do with REPL. If I am running the same code with `scalac`, it still behaves the same. If I put a return value of `as` to `T` - it throws, but if I put a return value of `Any` - it doesn't. I think I get it why it does that, but it still is a bug, - it should not matter what the return type is – Archeg Dec 10 '15 at 15:20
  • Can you show the code? How exactly are you calling it, and what do you do with the result? – Dima Dec 10 '15 at 15:23
  • `class Foo[T] { def as(x: Any):Any = x.asInstanceOf[T] } val f = new Foo[String]() val b = f.as(5)` This one compiles and run successfully. If you change `:Any` to `:T` - it stops working – Archeg Dec 10 '15 at 16:19
  • 1
    Right. Because when you are assigning the result of `as` to `b`, it knows statically that it is supposed to be a String, and gets upset that it isn't. Try something like `val b = { f.as(5); () }` – Dima Dec 10 '15 at 16:31
  • You are right, I see it now. Very tricky situation with `asInstanceOf` here, usually you do not expect so weird behavior from so simple method. Thanks for clarification – Archeg Dec 10 '15 at 16:33
  • Yeah, the thing is, it's not really a method. More like a compiler directive. – Dima Dec 10 '15 at 16:56
  • It looks like a method, but it isn't. And that introduces a lot of confusion. It'd have been better if it looked like a normal cast or something like `as` keyword in C# – Archeg Dec 10 '15 at 17:24
  • Thanks for the answer as well as the discussions! Before posting the question, I also went to check [their implementations](https://github.com/scala/scala/blob/2.12.x/src/library-aux/scala/Any.scala#L119) but unfortunately found nothing helpful there. Now it looks less confusion and easier to understand if think of asInstanceOf as a compiler directive (for compiler to insert corresponding "cast" and enforce type compatible in compile time) instead of a simple method. – QRush Dec 11 '15 at 04:11