I am studying generics in Java and I have stumbeled into an astonishing situation. I created the code below explicitly to generate an error, but instead I only get warnings, and what's even more surprising, is the fact that if we ignore the warnings, it works.
import java.util.ArrayList;
import java.util.Iterator;
class Test {
static public void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
ArrayList<String> list2 = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list2.add("Bonjour");
}
Printer<Integer, ArrayList> printer = new Printer<Integer, ArrayList>();
printer.Print(list2);
}
}
and
import java.util.Iterator;
import java.util.Collection;
public class Printer<T, E extends Iterable> {
public void Print(E data) {
Iterator<T> i = data.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
In the class Printer, I chose not to use a type parameter for the Iterable type E. From this question, the system is going to assume the type argument is object. Fair enough. So I am expecting an ArrayList of objects.
In the function Print of this class, I am creating an iterator over a type T, so this is more specefic than Object.
I expected that if I were to give an ArrayList of strings to the Print function, for example, and create an iterator of integers, there would be an error, because I cannot just cast a string to an integer just like that. But no, it actually compiles and works just fine. The output it gives is the following:
Printer.java:7: warning: [unchecked] unchecked conversion
Iterator<T> i = data.iterator();
^
required: Iterator<T>
found: Iterator
where T is a type-variable:
T extends Object declared in class Printer
1 warning
Printer.java:7: warning: [unchecked] unchecked conversion
Iterator<T> i = data.iterator();
^
required: Iterator<T>
found: Iterator
where T is a type-variable:
T extends Object declared in class Printer
1 warning
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Bonjour
Why does this compile in the first place? And how come it works?
Edit:
I tried to add this line to main:
Iterator<Integer> it = list2.iterator();
And it gives an error, compilation fails, which is what i expect. Why does the original code compile even tho the same thing is happening on this line :
Iterator<T> i = data.iterator();
You say "the system is going to assume the type argument is object", but that's only in regards to implementing interfaces. Iterable
doesn't behave in exactly the same way as Iterable<Object>
. In this case, writing Iterable
causes this line to only be a warning, not an error:
Iterator<T> i = data.iterator();
If you wrote Iterator<Object>
instead, the above line will not compile. The compiler doesn't allow you to convert from a Iterator<Object>
to Iterator<T>
, as it should.
Generic types that have no type parameters are called raw types. See the linked post for more details.
That explains why the code compiles, but what happens at runtime?
At runtime, the JVM doesn't care about generic types at all. There's no difference between Iterator<Integer>
and Iterator<String>
. From the perspective of the JVM, the code looks like:
public void Print(Iterable data) {
Iterator i = data.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
Surely you'd agree that this code should run without exceptions. There is no type conversion anywhere.
What would throw an exception, is if you take integers out of that Iterator<Integer>
(which is secretly an Iterator<String>
), and call methods that are specific to Integer
. For example:
ArrayList<String> list2 = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list2.add("Bonjour");
}
Iterable iterable = list2;
// the line below will not have been allowed if 'iterable' is not a raw type
Iterator<Integer> iterator = iterable.iterator();
while (iterator.hasNext()) {
// here I am calling the Integer.intValue method
System.out.println(iterator.next().intValue());
}
Since as far as the JVM is concerned, iterator.next()
returns an Object
, so intValue()
cannot be called directly. When compiling the line
System.out.println(iterator.next().intValue());
the compiler inserts a cast:
System.out.println(((Integer)iterator.next()).intValue());
And it is this cast is where an exception will occur.