...
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.
Noncompliant Code Example (
...
Variadic Arguments)
Heap pollution occurs because Java does not enforce type correctness on lists of lists. This program happily tries to build can occur without using raw types such as java.util.List
. This noncompliant code example builds a list of list of strings, while trying before passing it to sneak a single integer into the list. While the Java a modify
method. Because this method is variadic, it casts l
into an array of lists of strings. But Java is incapable of representing the types of parameterized arrays. This allows the modify()
method to sneak a single integer into the list. While the Java compiler emits several warnings, this program compiles and runs until it tries to extract the integer 42 from a List<String>, and throws a ClassCastException
.
Code Block | ||||
---|---|---|---|---|
| ||||
public class ListInspectorExampleListModifierExample { public static void inspect(List<List<String>>modify(List<String>... l) { Object[] objectArray = l; objectArray[1] = Arrays.asList(42); // pollutes list, no warning for (List<String> list : l) { for (String sstring : list) { // ClassCastException on 42 System.out.println(sstring); } } } public static void main(String[] args) { List<String> s = Arrays.asList("foo", "bar"); ListList<String> is2 = Arrays.asList(42); // warning about raw types"baz", "quux"); List<List<String>>modify( l = new ArrayList<List<String>>(s, s2); l.add(s); l.add(i); // unchecked varargs // warning about unchecked convertion inspect(l); } } |
Noncompliant Code Example (Array of lists)
The problem persists if the function parameter is replaced with an array of lists
Code Block | ||||
---|---|---|---|---|
| ||||
public class ListInspectorExample {
public static void inspect(List<String>[] l) {
for (List<String> list : l) {
for (String s : list) { // ClassCastException on 42
System.out.println(s);
}
}
}
public static void main(String[] args) {
List<String> s = Arrays.asList("foo");
List i = Arrays.asList(42); // warning about raw types
inspect(new List[] {s, i}); // warning about unchecked convertion
}
}
|
Noncompliant Code Example (Variadic Arguments)
The problem persists if the function parameter is changed to a variadic argument of List<String>
. During type erasure, the compiler translates this to a single array of objects.
This program produces the following output:
Code Block |
---|
foo
bar
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at ListModifierExample.modify(Java.java:13)
at ListModifierExample.main(Java.java:25)
at Java.main(Java.java:33)
|
Noncompliant Code Example (Array of lists of strings)
This noncompliant code example is similar, but it uses an explicit array of lists of strings as the single parameter to modify()
. The program again dies with a ClassCastException
from the integer 42 injected into a list of strings.
Code Block | ||||
---|---|---|---|---|
| ||||
class ListModifierExample {
public static void modify(List<String>[] l) {
Object[] objectArray = l; // Valid
objectArray[1] = Arrays.asList(42); // pollutes list, no warning
| ||||
Code Block | ||||
| ||||
public class ListInspectorExample { public static void inspect(List<String>... l) { for (List<String> list : l) { for (String sstring : list) { // ClassCastException on 42 System.out.println(sstring); } } } public static void main(String[] args) { List<String> s = Arrays.asList("foo", "bar"); List<String> s2 = Arrays.asList("foo("baz", "quux"); List il[] = Arrays.asList(42 {s, s2}; modify(l); // warning about raw types inspect( s, i); // warningunchecked about variadic generic parameterconversion warning } } |
Compliant Solution
...
(List of lists of strings)
This compliant solution uses a list of lists of strings as the argument to modify()
. This type safety enables the compiler to prevent the modify()
method from injecting an integer into the list. In order to compile, the modify()
method instead inserts a string, preventing heap pollutionAll such noncompliant examples can be mitigated by avoiding raw types. Simply replacing the List
of integers with a List<Integer>
causes the compiler to refuse to compile each noncompliant code example.
Code Block | ||||
---|---|---|---|---|
| ||||
public class ListInspectorExampleListModifierExample { public static void inspect(List<String>... l) {modify(List<List<String>> l) { l.set( 1, Arrays.asList("forty-two")); // no warning for (List<String> list : l) { for (String sstring : list) { // ClassCastException on 42 System.out.println(sstring); } } } public static void main(String[] args) { List<String> s = Arrays.asList("foo", "bar"); List<Integer>List<String> is2 = Arrays.asList(42); inspect( s, i);"baz", "quux"); List<List<String>> l = new ArrayList<List<String>>(); l.add(s); // improper argument types compiler errorl.add(s2); modify(l); } } |
Note that to avoid warnings, we cannot use Arrays.asList()
to build a list of list of strings, because that method is also variadic and would produce a warning about variadic arguments being parameterized class objects.
Risk Assessment
Mixing generic and nongeneric code can produce unexpected results and exceptional conditions.
...