Reputation: 418
I was reading about type erasure, here
case class Thing[T](value: T)
def processThing(thing: Thing[_]) = {
thing match {
case Thing(value: Int) => "Thing of int" //isn't thing Thing[_] anymore?
case Thing(value: String) => "Thing of string" //isn't thing Thing[_] anymore?
case Thing(value: Seq[Int]) => "Thing of Seq[Int]" //value=Seq[_] really
case Thing(value: Seq[String]) => "Thing of Seq[String]" //value=Seq[_] really
case _ => "Thing of something else"
}
}
println(processThing(Thing(Seq(1,2,3)))) //type erased, I get it
println(processThing(Thing(Seq("hello", "yo")))) //type erased, I get it
println(processThing(Thing(1))) //why is this working?
println(processThing(Thing("hello"))) //why is this working?
I understand why Seq[Int] and Seq[String] are not identified correctly, at runtime both are seen like Seq[Object].
However, I do not understand why the first two examples ARE working: why Thing[Int] and Thing[String], both being Thing[T] are NOT having the same problem that Seq[T] has...
Why Thing[Seq[T]] is type erased but Thing[T] (T=Int, String) is not?
Can anybody explain what is going on here? Thanks
Upvotes: 1
Views: 446
Reputation: 22850
You are right that at runtime both Thing(1)
and Thing("Hello")
will erase to the same class Thing
.
Thus, if you do something like this:
thing match {
case _: Thing[Int] => foo
case _: Thing[String] => bar
}
You would see the behavior you expected.
However, your pattern match is doing something different, it extracts the value inside thing
and then performs a class check on that.
The class information of the value is preserved by itself so that is why you can distinguish between Int
, String
and Seq
, but you can't see what was the type parameter of the Seq
But, you may try to check the first element of the Seq
... but that still won't be enough, since the first element may be a Dog
and the second a Cat
because it was a Seq[Animal]
and that check is even more unsafe than the previous ones since the Seq
may be empty.
Upvotes: 5
Reputation: 159905
Consider the following Java code:
class Foo {
<T> void bar(T whatIsIt) {
System.out.println("It is a: " + whatIsIt.getClass().getName());
}
public static void main() {
Foo f = new Foo();
f.bar(123);
f.bar("Hello world");
f.bar(f);
f.bar(new ArrayList<String>());
}
}
At runtime there exists only one method Foo#bar(Object)
. But the object that is passed in as whatIsIt
still has a pointer to whatever class it actually is.
Scala has access to the same metadata that Java does. So when it builds the bytecode for processThing
it can insert value instanceOf Integer
- and that's what it does:
scala> :javap processThing
// ... snip ...
public java.lang.String processThing($line3.$read$$iw$Thing<?>);
descriptor: (L$line3/$read$$iw$Thing;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=8, args_size=2
0: aload_1
1: astore_3
2: aload_3
3: ifnull 29
6: aload_3
7: invokevirtual #37 // Method $line3/$read$$iw$Thing.value:()Ljava/lang/Object;
10: astore 4
12: aload 4
14: instanceof #39 // class java/lang/Integer
17: ifeq 26
20: ldc #41 // String Thing of int
// ... snip ...
Upvotes: 2