Why is it possible to insert a String
into a List<Integer>
in the following code? I have a class which inserts numbers into a List of Integers:
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(2);
list.add(3);
list.add(4);
Inserter inserter = new Inserter();
inserter.insertValue(list);
System.out.print(list);
}
}
Then I have a separate class which inserts a String
into a List
, with the numeric string value "42"
:
public class Inserter {
void insertValue(List list)
{
list.add(new String("42"));
}
}
Why does the compiler not raise a compiler error, or the runtime throw a runtime exception such as *CastException
, when I add the String
to the List of Integer? Also, why does System.out.print(list)
produce output like the following without throwing any exceptions?
[2, 3, 4, 42]
What is the fundamental reason that allows all this to happen?
This is probably an example to illustrate type erasure for generics (I recommend reading that link to fully understand this and the crucial role it plays in Java generics).
list
is declared as a List<Integer>
- When it is passed as an argument to
listValue
it is cast to a raw type
- From this point onwards in that method the program at run time has no knowledge that it was originally a "list of Integer", so it can insert any object with no exception - within the class declaration the type variable is erased to
Object
†
- In the main method the
print
command simply calls toString
on the list, which doesn't mind what it contains, and so it prints the elements including the string.
If you want to see an exception, try adding a line:
Integer myInt = list.get(3); // try to get the string
This will throw a ClassCastException
as the compiler, during erasure, inserts casts where necessary to protect type safety.
† Casts of parameterized types, such as List<Integer>
to the raw type, such as List
, give you a compiler warning to tell you that exactly this kind of problem might be about to happen. You can suppress that warning with @SuppressWarnings("unchecked")
(or "rawtypes"
, if it is available to you). This is a good way of "acknowledging" that you are about to do something that will lead to an unchecked runtime exception, and also helps tell other future coders that something funky might be about to happen. E.g.:
void insertValue(@SuppressWarnings("unchecked") List list)
{
list.add(new String("42"));
}
The compiler will throw
Exception in thread "main" java.lang.ClassCastException:
java.lang.String cannot be cast to java.lang.Integer 234 at
com.performance.Main.main(Main.java:26)
as you specified the type here ArrayList<Integer>();
and when you are adding a string list.add(new String("42"));