Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in type erasure of List[Int] and List[Integer]

Why does List[scala.Int] type erase to List[Object] whilst Integer in List[java.lang.Integer] seems to be preserved? For example, javap for

object Foo {
  def fooInt: List[scala.Int] = ???
  def fooInteger: List[java.lang.Integer] = ???
}

outputs

public scala.collection.immutable.List<java.lang.Object> fooInt();
public scala.collection.immutable.List<java.lang.Integer> fooInteger();

where we see Integer was preserved in second case. The docs state

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

Is this perhaps due to the "bounds" clause? If so, where is this bound specified?

like image 239
Mario Galic Avatar asked Jul 18 '19 17:07

Mario Galic


People also ask

What are the two 2 types of Erasure?

- Erasure is a type of alteration in document. It can be classified as chemical erasure and physical erasure.

What is the point of type erasure?

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

What is Scala type erasure?

Type erasure refers to the runtime encoding of parameterized classes in Scala. It is simply performed by Scala compiler in which it removes all the generic type information after compilation. In Scala, generics are erased at runtime, which means that the runtime type of List[Int] and List[Boolean] is actually the same.

Does C# have type erasure?

C# (actually . NET generics in general) does not use type erasure. Java uses type erasure. C# does not.


1 Answers

I am not a scala developer, take this with a grain of salt. The erasure is the same:

public static scala.collection.immutable.List<java.lang.Object> fooInt();
descriptor: ()Lscala/collection/immutable/List;

public static scala.collection.immutable.List<java.lang.Integer> fooInt();
descriptor: ()Lscala/collection/immutable/List;

look at the descriptor parameter; that is what gets referenced at call sites at the byte code level.

When you simply do javap, it "cheats" a little bit by looking at the Signature parameter (read further) so that it shows you this little inoffensive lie.

Now think about it. Let's take this method and place it in class A:

static List<Integer> test() {
    return null; // or whatever that is not the point
} 

we compile it, share the .class file to someone else. That someone else uses it in this form: (without actually having source code for A).

public void testMe() {
    Integer x = A.test().get(0);
}

if you look at the byte-code, you will see:

    5: invokeinterface #3,  2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    10: checkcast     #4      // class java/lang/Integer

There is an immediate question that has to arise: how does it know about Integer (via that checkcast) if generics are erased? The answer is the optional Signature that is generated when A is compiled, or in your cases:

 ()Lscala/collection/immutable/List<Ljava/lang/Object;>; //fooInt
 ()Lscala/collection/immutable/List<Ljava/lang/Integer;>; // fooInteger

This Signature information is what is used by the compiler to enforce type safety at callsites, via runtime checks; if this field would not be present - that would have been impossible.

Now to why the Signature of scalac generates Object (thus zero type safety for callers) is something that the duplicate addresses. I've tried to read the issue and it's not an easy read- I'll just go with "I trust you".


A little more explanations: Signature appeared in java-5 when generics where added. Until then, all call-sites where referenced by descriptor, changing that to Signature instead would mean that existing code would break; thus never done. Thus Signature became optional and used in a different way - for checkcast. At least this is what I am strongly inclined to assume :)

like image 51
Eugene Avatar answered Sep 30 '22 17:09

Eugene