Parameterized classes have expectations of the objects that they reference; they expect certain objects to match their paramaterized types. Heap pollution occurs when a parameterized class references an object that it expects to be of the parameterized type, but the object is of a different type. For more information on heap pollution, see the Java Language Specification, §4.12.2.1, "Heap Pollution," [JLS 2005]). For instance, consider the following code snippet.
Code Block |
---|
List llist = new ArrayList();
List<String> ls = llist; // Produces unchecked warning
|
...
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, 142);
System.out.println(list.get(0)); // throws ClassCastException
}
}
|
...
Code Block |
---|
|
class ListUtility {
private static void addToList(List<String> list, String str) {
list.add(str); // No warning generated
}
public static void main(String[] args) {
List<String> list = new ArrayList<String> ();
addToList(list, "142");
System.out.println(list.get(0));
}
}
|
...
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, 142);
System.out.println(list.get(0));
}
}
|
...
This noncompliant code example compiles and runs cleanly because it suppresses the unchecked warning produced by the raw List.add()
method. The printOneprintNum()
method intends to print the value 142, either as an int
or as a double
depending on the type of the variable type
.
Code Block |
---|
|
class ListAdder {
@SuppressWarnings("unchecked")
private static void addToList(List list, Object obj) {
list.add(obj); // unchecked warning suppressed
}
private static <T> void printOneprintNum(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, 142);
System.out.println(list.get(0));
}
public static void main(String[] args) {
double d = 142;
int i = 142;
System.out.println(d);
ListAdder.printOneprintNum(d);
System.out.println(i);
ListAdder.printOneprintNum(i);
}
}
|
However, despite list
being correctly parameterized, this method prints 1 42 and never 142.0 because the int
value 1 42 is always added to list
without being type checked. This code produces the following output:
Code Block |
---|
142.0
142
142
1
42 |
Compliant Solution (Parameterized Collection)
...
Code Block |
---|
|
class ListAdder {
private static <T> void addToList(List<T> list, T t) {
list.add(t); // No warning generated
}
private static <T> void printOneprintNum(T type) {
if (type instanceof Integer) {
List<Integer> list = new ArrayList<Integer>();
addToList(list, 142);
System.out.println(list.get(0));
}
else if (type instanceof Double) {
List<Double> list = new ArrayList<Double>();
addToList(list, 142.0); // will not compile with 142 instead of 142.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 = 142;
int i = 142;
System.out.println(d);
ListAdder.printOneprintNum(d);
System.out.println(i);
ListAdder.printOneprintNum(i);
}
}
|
This code compiles cleanly and produces the correct output:
Code Block |
---|
142.0
142.0
142
142 |
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 printOneprintNum()
can be used, but no warnings result if addToList(list, 142)
is used instead of addToList(list, 142.0)
. Great care must be taken to ensure type safety when generics are mixed with nongeneric code.
...
Heap pollution can occur without using raw types such as java.util.List
. This noncompliant code example builds a list of list of strings, before passing it to a modify
method. Because this method is variadic, it casts l
list
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>.
Code Block |
---|
|
class ListModifierExample {
public static void modify(List<String>... llist) {
Object[] objectArray = llist;
objectArray[1] = Arrays.asList(42); // pollutes list, no warning
for (List<String> listls : llist) {
for (String string : listls) { // ClassCastException on 42
System.out.println(string);
}
}
}
public static void main(String[] args) {
List<String> s = Arrays.asList("foo", "bar");
List<String> s2 = Arrays.asList("baz", "quux");
modify( s, s2); // unchecked varargs warning
}
}
|
...
Code Block |
---|
|
class ListModifierExample {
public static void modify(List<String>[] llist) {
Object[] objectArray = llist; // Valid
objectArray[1] = Arrays.asList(42); // pollutes list, no warning
for (List<String> listls : llist) {
for (String string : listls) { // ClassCastException on 42
System.out.println(string);
}
}
}
public static void main(String[] args) {
List<String> s = Arrays.asList("foo", "bar");
List<String> s2 = Arrays.asList("baz", "quux");
List llist[] = {s, s2};
modify(llist); // unchecked conversion warning
}
}
|
...
Code Block |
---|
|
class ListModifierExample {
public static void modify(List<List<String>> llist) {
llist.set( 1, Arrays.asList("forty-two")); // no warning
for (List<String> listls : llist) {
for (String string : listls) { // ClassCastException on 42
System.out.println(string);
}
}
}
public static void main(String[] args) {
List<String> s = Arrays.asList("foo", "bar");
List<String> s2 = Arrays.asList("baz", "quux");
List<List<String>> llist = new ArrayList<List<String>>();
llist.add(s);
llist.add(s2);
modify(llist);
}
}
|
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.
...