...
Code Block | ||
---|---|---|
| ||
class ListUtility {
private static void addToList(List list, Object obj) {
list.add(obj); // unchecked warning
}
public static void main(String[] args) {
List<String> list = new ArrayList<String> ();
addToList(list, 1);
System.out.println(list.get(0)); // throws ClassCastException
}
}
|
When executed, this code throws an exception. This happens not because a List<String>
receives an Integer
but because the value returned by list.get(0)
is an improper type (an Integer
rather than a String
). In other words, the code throws an exception some time after the execution of the operation that actually caused the error, complicating debugging.
...
Suppose that the addToList()
method was is legacy code that could not cannot be changed. The following compliant solution creates a checked view of the list by using the Collections.checkedList()
method. This method returns a wrapper collection that performs runtime type checking in its implementation of the add()
method before delegating to the backend List<String>
. The wrapper collection can be safely passed to the legacy addToList()
method.
Code Block | ||
---|---|---|
| ||
class ListUtility {
private static void addToList(List list, Object obj) {
list.add(obj); // Unchecked warning, also throws ClassCastException
}
public static void main(String[] args) {
List<String> list = new ArrayList<String> ();
List<String> checkedList = Collections.checkedList(list, String.class);
addToList(checkedList, 1);
System.out.println(list.get(0));
}
}
|
...
Code Block | ||
---|---|---|
| ||
class ListAdder { @SuppressWarnings("unchecked") private static void addToList(List list, Object obj) { list.add(obj); // Uncheckedunchecked warning suppressed } private static <T> void printOne(T type) { if (!(type instanceof Integer || type instanceof Double)) { System.out.println("Cannot print in the supplied type"); } List<T> list = new ArrayList<T>(); addToList(list, 1); System.out.println(list.get(0)); } public static void main(String[] args) { double d = 1; int i = 1; System.out.println(d); ListAdder.printOne(d); System.out.println(i); ListAdder.printOne(i); } } |
However, despite list
being correctly parameterized, this method prints 1 and never 1.0 because the int
value 1 is always added to list
without being type checked. This code produces the following output:
Code Block |
---|
1.0 1 1 1 |
Compliant
...
Solution (Parameterized Collection)
This compliant solution generifies the addToList()
method, eliminating any possible type violations.
Code Block | ||
---|---|---|
| ||
class ListAdder { private static <T> void addToList(List<T> list, T t) { list.add(t); // No warning generated } private static <T> void printOne(T type) { if (type instanceof Integer) { List<Integer> list = new ArrayList<Integer>(); addToList(list, 1); System.out.println(list.get(0)); } else if (type instanceof Double) { List<Double> list = new ArrayList<Double>(); // This will not compile if addToList(list, 1.0); is// used will not compile with 1 instead addToList(list,of 1.0); System.out.println(list.get(0)); } else { System.out.println("Cannot print in the supplied type"); } } public static void main(String[] args) { double d = 1; int i = 1; System.out.println(d); ListAdder.printOne(d); System.out.println(i); ListAdder.printOne(i); } } |
This code compiles cleanly and produces the correct output:
Code Block |
---|
1.0
1.0
1
1
|
If the method addToList()
is externally defined (such as in a library or as an upcall method) and cannot be changed, the same compliant method printOne()
can be used, but no warnings result if addToList(list, 1)
is used instead of addToList(list, 1.0)
. Great care must be taken to ensure type safety when generics are mixed with nongeneric code.
...