To my knowledge, Java drops the generic type argument information during runtime. It is only used on compilation to perform checks, for example, is this particular method call valid or not.
Today I came across the following piece of code, in which, seemingly, Java determines by the collection/list type argument, which constructor to call:
public static class MyClass {
public MyClass(final Collection<String> coll) {
System.out.println("Constructor 1");
}
public MyClass(final List<Integer> list) {
System.out.println("Constructor 2");
}
}
The following calls are made:
new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2
Now, if I erase the type arguments:
public static class MyClass2 {
public MyClass2(final Collection coll) {
System.out.println("Constructor 1");
}
public MyClass2(final List list) {
System.out.println("Constructor 2");
}
}
...The same calls act as I would expect them to; constructor invocation which uses a list argument goes for the constructor which meets its needs "most precisely":
new MyClass2(new HashSet<String>()); // Constructor 1
new MyClass2(new ArrayList<String>()); // Constructor 2
new MyClass2(new ArrayList<Integer>()); // Constructor 2
It appears that generics information is being stored in the compiled class (in this case, MyClass) and is not being discarded after all, but it should be discarded. What am I misunderstanding?
What happens here is that the compiler can distinguish the two constructors by using generics, so it does before creating the byte code and before stripping generics.
In the middle case, it will tell the VM to invoke MyClass2<init>(Collection)
(i.e. generate byte code which matches this specific constructor).
The VM doesn't try to determine which method matches at runtime. That would be way too slow. Instead, it relies on the compiler creating very specific instructions.
This is why the code above works even though the generic information has been erased at runtime.
[EDIT] To clarify: The byte code contains additional information which the compiler can see and use. You can get the same information via reflection.
Erasure means that the byte code interpreter and JIT doesn't care about generics which is why you can't have setFoo(List<String>)
and setFoo(List<Integer>)
in the same class: While the compiler could distinguish the two, the runtime can't.
Specifically, when you examine a method via reflection, you will get generics information but the byte code interpreter / JIT doesn't use reflection. Instead, it uses the compressed method signature which reads something like Method com/pany/Type/setFoo(Ljava.util.List;)V
- no generics here anymore.
Related:
Didn't notice that you are dealing with constructor. Anyways, the below arguments is valid even for constructor.
Method invocation for the overloaded method is resolved at compile time by the compiler. And generics are used for type check at compile time only. So, this has nothing to do with the type erasure, which is completely a runtime business.
public MyClass(final Collection<String> coll)
public MyClass(final List<Integer> list)
Now, when you invoke the method as:
new MyClass(new HashSet<String>()); // Constructor 1
new MyClass(new ArrayList<String>()); // Constructor 1
new MyClass(new ArrayList<Integer>()); // Constructor 2
Compiler will decide which method has the more specific type for the passed argument. Consider case 2, where is your main doubt I guess.
ArrayList<String>
is a subtype of Collection<String>
, but it's not a subtype of List<Integer>
. Generics are invariant. So, compiler will bind the 2nd method invocation to the first method.
public MyClass2(final Collection coll)
public MyClass2(final List list)
Now, ArrayList<String>
is a subtype of List
and Collection
both. But List
is more specific to ArrayList
than Collection
. So, compiler will bind the method call:
new MyClass2(new ArrayList<String>());
with the one taking List
as argument.
References:
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