CodeGym /Java Blog /Java Generics /Using varargs when working with generics
Author
John Selawsky
Senior Java Developer and Tutor at LearningTree

Using varargs when working with generics

Published in the Java Generics group
Hi! In today's lesson, we'll continue to study generics. As it happens, this is a big topic, but there's no avoiding it — it's an extremely important part of the language :) When you study the Oracle documentation on generics or read online tutorials, you will come across the terms non-reifiable types and reifiable types. A reifiable type is a type for which information is fully available at runtime. In Java, such types include primitives, raw types, and non-generic types. In contrast, non-reifiable types are types whose information is erased and becomes inaccessible at runtime. As it happens, these are generics — List<String>, List<Integer>, etc.

By the way, do you remember what varargs is?

In case you forgot, this is a variable-length argument. They are useful in situations where we don't know how many arguments might be passed to our method. For example, if we have a calculator class that has a sum method. The sum() method can receive 2 numbers, or 3, or 5, or as many as you like. It would be very strange to overload the sum() method for every possible number of arguments. Instead, we can do this:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Console output:

15
11
This shows us that there are some important features when using varargs in combination with generics. Let's look at the following code:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
The addAll() method takes as input a List<E> and any number of E objects, and then it adds all these objects to the list. In the main() method, we call our addAll()method twice. In the first case, we add two ordinary strings to the List. Everything is in order here. In the second case, we add two Pair<String, String> objects to the List. But here we unexpectedly receive a warning:

Unchecked generics array creation for varargs parameter
What does that mean? Why do we get a warning and why is there any mention of an array? After all, our code doesn't have an array! Let's start with the second case. The warning mentions an array because the compiler converts the variable-length argument (varargs) to an array. In other words, the signature of our addAll() method is:

public static <E> void addAll(List<E> list, E... array)
It actually looks like this:

public static <E> void addAll(List<E> list, E[] array)
That is, in the main() method, the compiler converts our code into this:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
A String array is just fine. But a Pair<String, String> array is not. The problem is that Pair<String, String> is a non-reifiable type. During compilation, all information about type arguments (<String, String>) is erased. Creating arrays of a non-reifiable type is not allowed in Java. You can see this if you try to manually create a Pair<String, String> array

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
The reason is obvious: type safety. As you will recall, when creating an array, you definitely need to specify which objects (or primitives) the array will store.

int array[] = new int[10];
In one of our previous lessons, we examined type erasure in detail. In this case, type erasure causes us to lose the information that the Pair objects store <String, String> pairs. Creating the array would be unsafe. When using methods that involve varargs and generics, be sure to remember about type erasure and how it works. If you are absolutely certain about the code you've written and know that it will not cause any problems, you can turn off the varargs-related warnings using the @SafeVarargs annotations.

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
If you add this annotation to your method, the warning we encountered earlier will not appear. Another problem that can occur when using varargs with generics is heap pollution. Using varargs when working with generics - 3Heap pollution can happen in the following situation:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Console output:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
In simple terms, heap pollution is when objects of type A should be in the heap, but objects of type B end up there due to errors related to type safety. In our example, this is precisely what happens. First, we created the raw numbers variable and assigned a generic collection (ArrayList<Number>) to it. Then we added the number 1 to the collection.

List<String> strings = numbers;
On this line, the compiler tried to warn us of possible errors by issuing the "Unchecked assignment..." warning, but we ignored it. We end up with a generic variable of type List<String> that points to a generic collection of type ArrayList<Number>. Clearly, this situation can lead to trouble! And so it does. Using our new variable, we add a string to the collection. We now have heap pollution — we added a number and then a string to the parametrized collection. The compiler warned us, but we ignored its warning. As a result, we get a ClassCastException only while the program is running. So what does this have to do with varargs? Using varargs with generics can easily lead to heap pollution. Here's a simple example:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
What's going on here? Due to type erasure, our variable-length argument

List<String>...stringsLists
becomes an array of lists, i.e. List[], of objects of an unknown type (don't forget that varargs turns into a regular array during compilation). Because of this, we can easily assign it to the Object[] array variable in the first line of the method — the type of the objects in our lists has been erased! And now we have an Object[] variable, to which we can add anything at all, since all objects in Java inherit Object! At first, we only have an array of lists of strings. But thanks to type erasure and our use of varargs, we can easily add a list of numbers, which we do. As a result, we pollute the heap by mixing objects of different types. The result will yet another ClassCastException when we try to read a string from the array. Console output:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Such unexpected consequences can be caused by using varargs, a seemingly simple mechanism :) And with that, today's lesson comes to an end. Don't forget to solve a couple of tasks, and if you have time and energy, study some additional reading. "Effective Java" won't read itself! :) Until next time!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION