I have the following test code. I'm trying to understand the interoperability between Generics and legacy.
List myList = new ArrayList();
myList.add("abc");
myList.add(1);
myList.add(new Object());
System.out.println("Printing the unchecked list");
Iterator iterator = myList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
List<String> strings = myList;
System.out.println("Printing the unchecked list assigned to List<String> using iterator");
Iterator stringIterator = strings.iterator();
while (stringIterator.hasNext()) {
System.out.println(stringIterator.next()); // this works fine! why? shouldn't it fail converting the 1 (int/Integer) to String?
}
System.out.println("Printing the unchecked list assigned to List<String> using for");
for (int i = 0; i != strings.size(); i++) {
System.out.println(strings.get(i)); // blows up as expected in the second element, why?
}
System.out.println("Printing the unchecked list assigned to List<String> using foreach");
for (String s : strings) {
System.out.println(s); // blows up as expected in the second element, why?
}
Why does the iterator.next
work fine when I try to print it while the System.out.println
blows up as expected when I iterate using for
loops?
The key thing to remember about generics is that it's simply a way to omit casts from the source code. The casts are inserted by the compiler. So, code fails (or does not fail) because of the presence (or absence) of a cast.
- this works fine! why?
Because stringIterator
is raw, stringIterator.next()
isn't cast to anything: it's just read as an Object
, which is the erased return type.
- blows up as expected in the second element, why?
strings
is a List<String>
, so the result of strings.get(i)
is assumed to be String
, and println(String)
is chosen to be invoked, rather than println(Object)
. As such, a cast is inserted. strings.get(1)
isn't a String
, so this fails with a ClassCastException
.
Interestingly, had you been trying this with a List<Integer>
, this wouldn't have failed, because println(Object)
would be invoked, and no cast would then have been necessary.
- blows up as expected in the second element, why?
Because there's a cast to String
inserted, to assign the element to String s
.
Refer to JLS 14.14.2 for the desugared form of the enhanced for loop:
for (I #i = Expression.iterator(); #i.hasNext(); ) { {VariableModifier} TargetType Identifier = (TargetType) #i.next(); Statement }
So, your code is equivalent to:
for (Iterator<String> it = strings.iterator(); it.hasNext(); ) {
String s = (String) it.next(); // Actually, your code fails on this line.
System.out.println(s);
}