What is Heap Pollution in Java - Varargs Heap Pollution With Examples

1- Introduction

In the Java programming language, heap pollution is a term that case occurs when a variable of a parameterized type refers to an object that is not of that parameterized type.
This case can be detected at compilation and marked with an unchecked warning. Then, heap pollution often causes ClassCastException at runtime.

In short, Heap pollution refers incorrect data in the heap memory.

2- Heap Pollution Example

Let's give a few code examples for the heap collection;

    public static void main(String[] args) {
        List list = new ArrayList<Integer>();
        List<String> listOfString = list;     // unchecked warning

        list.add("String Value");   // index 0
        list.add(1);                // index 1


        String str = listOfString.get(0);     // "String Value"
        String str2 = listOfString.get(1);    // INT (1) - ClassCastException
    }
  • Here, list variable is created as an array without a parameter type.
    Next, reference value of list variable is assigned to the array variable listOfString -  which is defined as an array with a String parameter type.
  • Then, String ("String value") and Integer (1) values are added to the list variable.
    From the listOfString which is previously assigned variable;
    - We can successfully assign 0. index value (String) from to "list" to the String variable.
    - However, when we try to assign 1st value from the "list" to the String variable, ClassCastException is thrown. Because first index value is an integer type and assigning it to a String variable causes heap pollution.

2.1 - Points to Consider in Heap Pollution

  • Heap Pollution can be prevented by using a type parameter in generics.
    List list = new ArrayList<Integer>(); //Unsafe
    
    List<Integer> list = new ArrayList<Integer>(); //Safe

  • Care should be taken in type conversions and assignments.

3- Varargs - Heap Pollution

When we use the varargs parameter, it is possible to potentially fall into a heap pollution state.

To read the article about varargs ->

In short; Every time we use varargs, the Java compiler creates an array to hold the given parameters

3.1 - Varargs Heap Pollution Example

Let's defineheapPollutionMethod(List... stringLists) method.

    public static void heapPollutionMethod(List<String>... stringLists) {
        List<Integer> intList = Arrays.asList(10, 20);
        Object[] objectArray = stringLists;
        objectArray[2] = intList; // Heap pollution
        String str = stringLists[0].get(0);  // STRING "A"
        String str2 = stringLists[1].get(1); // STRING "E"
        String str3 = stringLists[2].get(1); // ClassCastException - (20) INT
    }

Let's invoke the heapPollutionMethod from main method.

    public static void main(String[] args) {
        List<String> stringList1 = Arrays.asList("A", "B", "C");
        List<String> stringList2 = Arrays.asList("D", "E", "F");
        List<String> stringList3 = Arrays.asList("G", "H");

        heapPollutionMethod(stringList1, stringList2, stringList3);
    }
  • stringList1, stringList2, stringList3 are created with some values as String arrays. Then, these arrays are passed to the heapPollutionMethod as arguments.
  • (List... stringList) The varargs parameter (varargs list) holds the reference values ​​of the String arrays.
  • Next, List<Integer> intList is defined with 10 and 20 int values.
  • The reference values of the stringLists (varargs parameter) assigned to the Object[] objectArray.
  • The reference value of the int array assigned to the objectArray[2] index with objectArray[2] = intList; - This is where heap pollution occurs.
  • "A" value with stringLists[0].get(0);, "E" value with stringLists[1].get(1); are successfully assigned to the String variables.
  • However, when we try to assign the value of stringLists[2].get(1); (3) to the String str3 variable then ClassCastException error is thrown.

3.2 - SafeVarArgs Annotation  - @SafeVarArgs

SafeVarArgs annotation does not prevent the heap pollution. The relevant method is marked with this annotation, so that " Heap Pollution Warning" will not be given by compiler anymore.

// Possible heap pollution from parameterized vararg type 
public static void method(List<Integer>... list) {
		//codes
	}
	@SafeVarargs // No more warning
	public static void method(List<Integer>... list) {
		//codes
	}

Let's understand this subject better with one more example.

3.3 - Varargs Heap Pollution -  Example - 2

Generic Methods - VarArgs Usage (No Error)

Let's define <T> T[] toArray(T... arguments) and displayData() for printing output in the console.

    public static <T> T[] toArray(T... values) {
        System.out.println("Class type of values is: " + values.getClass());
        System.out.println("Class type of first value is: " + values[0].getClass());
        System.out.println("Class type of second value is: " + values[1].getClass());
        return values;
    }


    public static <T> void displayData(T... values) {
        System.out.println("[T] Number of arguments - [values.length]: " + values.length);
        for (T value : values) {
            System.out.println(value + " ");
        }
    }

From main method;

    public static void main(String[] args) {
        String[] list = toArray("String Value 1", "String Value 2");
        displayData(list);
    }

Output;

Class type of values is: class [Ljava.lang.String;
Class type of first value is: class java.lang.String
Class type of second value is: class java.lang.String
[T] Number of arguments - [values.length]: 2
String Value 1 
String Value 2 
  • With the invoking of toArray("String Value 1", "String Value 2"); two arguments are passed to Varargs parameter. Since it is a varargs parameter, it is basically added to an array.
  • As we can see in the output, values array variable (varargs parameter)  is a type of String. Class type of values is: class [Ljava.lang.String;
  • Also, we printed that in the console passed two values types are String types.
  • Since values array type is a String type, values array is assigend to String[] list without any problem. (No ClassCastException)
  • Later, we can write all values to the console with the invoking of displayData(list); method.

Let's define combineTwoAndReturnAsArray(T value1, T value2) and <T> T[] toArray(T... values) methods.

    public static <T> T[] combineTwoAndReturnAsArray(T value1, T value2) {
        System.out.println("[combineTwoAndReturnAsArray] - Class type of value1 is: " + value1.getClass());
        System.out.println("[combineTwoAndReturnAsArray] - Class type of value2 is: " + value2.getClass());
        System.out.println();
        return toArray(value1, value2);
    }

    public static <T> T[] toArray(T... values) {
        System.out.println("[toArray] - Class type of values is: " + values.getClass());
        System.out.println("[toArray] - Class type of first value is: " + values[0].getClass());
        System.out.println("[toArray] - Class type of second value is: " + values[1].getClass());
        displayData(values);
        return values;
    }

    public static void displayData(String... values) {
        System.out.println("[STRING] Number of arguments - [values.length]: " + values.length);
        for (String value : values) {
            System.out.println(value + " ");
        }
    }
    public static <T> void displayData(T... values) {
        System.out.println("[T] Number of arguments - [values.length]: " + values.length);
        for (T value : values) {
            System.out.println(value + " ");
        }
    }

From main method;

    public static void main(String[] args) {
        String[] list = combineTwoAndReturnAsArray("String Value 1", "String Value 2");
        displayData(list);
    }

Output:

[combineTwoAndReturnAsArray] - Class type of value1 is: class java.lang.String
[combineTwoAndReturnAsArray] - Class type of value2 is: class java.lang.String

[toArray] - Class type of values is: class [Ljava.lang.Object;
[toArray] - Class type of first value is: class java.lang.String
[toArray] - Class type of second value is: class java.lang.String
[T] Number of arguments - [values.length]: 2
String Value 1 
String Value 2 
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
  1. Two arguments are passed with the invoking of combineTwoAndReturnAsArray("String Value 1", "String Value 2");
  2. We printed the types of values which are sent to combineTwoAndReturnAsArray method.
    combineTwoAndReturnAsArray] Class type of value1 is: class java.lang.String
    [combineTwoAndReturnAsArray] Class type of value2 is: class java.lang.String

  3. Then, those values are sent to the toArray(T... values) method.
  4. When we want to print the types of all values in toArray method;
    - [toArray] - Class type of values is: class [Ljava.lang.Object;
    - [toArray] - Class type of first value is: class java.lang.String
    - [toArray] - Class type of second value is: class java.lang.String

    - It can be seen that T... values the class type varargs method parameter (values) is a type of Object (which is created by java).  
    - On the other hand, the passed values ​​in the array (varargs) are types of String.
    Here expects that varargs parameter as array should be type of T[]. But it is not an instance of T but instance of Object at the runtime.
  5. Then, we can write the values to the console with the invoking of displayData(values); method.
  6. In the toArray method, the return type is a type of Object Class via return values. Thus, ClassCastException will be thrown by on return to the;
    String[] list = combineTwoAndReturnAsArray("String Value 1", "String Value 2");
    class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String;
    - Object to String Casting fails, because Object[] is not a subtype of String[].

Note: In Java it is impossible to create an array of Generic type parameter new T[] { ... }. So, an Object[] array is created by java in order to hold the passed values ​​from the T generic type (Object is upper bound of "T" in our example). Therefore, the class type of created array may cause a problem (ClassCastException).

So last two examples shows that "it is unsafe to give another method access to a generic varargs parameter array."

3.4 Varargs Heap Pollution - Points To Consider

There are a few points to consider in order to prevent heap pollution;

  • Not using the varargs parameter with generic types.
  • Not converting an Object array to an array of a generic type.
  • Not making accessible the Varargs parameter (or method) to the other methods.

4- Conclusion

As a result, we have examined what heap pollution is and how it occurs. Also, We examined the possibility of potential heap pollution using varargs as well. Morever, We have shown them with some examples.

References

https://en.wikipedia.org/wiki/Heap_pollution
https://docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html#heap_pollution