Let's say I have a class with raw type declaration as List (list1). It's just a simple example:
public class Wildcards {
public boolean contains(List list1, List<?> list2){
/*for(Object element: list1) {
if (list2.contains(element)) {
return true;
}
}*/
list1.add("12sdf34"); //insert String
return false;
}
}
In list1 I insert String value. (If I use unbounded wildcards for list1 as for list2 it would be more secure and it would be compilation error). However here is a raw type.
Now let's use this method as following:
List<Integer> list1 = new ArrayList<Integer>();
List<Double> list2 = new ArrayList<Double>();
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0));
I will not get any errors and receive the following result:
Contains? false
List1 element: 12sdf34
Could anyone explain how it might be as I initialized list1 as List of Integers?
The answer is two-folded. First, generics are erased during compilation. The second part is the actual implementation of ArrayList
. Let's start with type erasure.
At compile time, all generic types are replaced with their upper bound. For example a generic parameter <T extends Comparable<T>>
collapses to Comparable<T>
. If no upper bound is given, it is replaced with Object
. This makes generics an efficient tool for type-checking at compile-time, but we loose all type information at runtime. Project Valhalla may or may not fix that in the future. Since your method deals with raw- and unbounded types, the compiler assumes Object
as generic type and thus list1.add("12sdf34");
passes type checking.
So why don't you get some exception at runtime? Why does ArrayList
not "recognize" that the value you give it is of wrong type? Because ArrayList
uses an Object[]
as its backing buffer. Next logical question is: Why does ArrayList
use an Object[]
instead of an T[]
? Because of type erasure: we cannot instantiate anything of T
or T[]
at runtime. Furthermore, the fact that arrays are covariant and retained, while generics are invariant and erased, makes for an explosive product if these two are mixed.
For your program, that means that neither a compilation-error nor a runtime exception will be thrown and thus your ArrayList<Integer>
may contain a String
. You will, however, get into troubles by writing
...
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0))
int i = list1.get(0);
From the view of the lexer, the code is still valid. But at runtime, the assignment will generate a ClassCastException
. This is one of the reasons why raw types should be avoided.