JavaRush /Java Blog /Random EN /Using varargs when working with generics

Using varargs when working with generics

Published in the Random EN group
Hello! In today's lesson we will continue to study generics. It just so happens that this is a big topic, but there is nowhere to go - this is an extremely important part of the language :) When you study the Oracle documentation on generics or read guides on the Internet, you will come across the terms Non-Reifiable Types and Reifiable Types . What kind of word is “Reifiable”? Even if everything is good with English, you are unlikely to have met it. Let's try to translate! Using varargs when working with generics - 2
*thank you Google, you helped a lot -_-*
A reifiable-type is a type whose information is fully available at runtime. In the Java language, these include primitives, raw-types, and non-generic types. In contrast, Non-Reifiable Types are types whose information is erased and made unavailable at runtime. These are just generics - List<String> , List<Integer> , etc.

By the way, do you remember what varargs are ?

In case you forgot, these are variable length arguments. They come in handy in situations where we don't know exactly how many arguments can be passed to our method. For example, if we have a calculator class and it has a method sum. sum()You can pass 2 numbers, 3, 5, or as many as you like to the method . It would be very strange to overload the method every time sum()to take into account all possible options. 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
So, use varargsin combination with generics has some important features. Let's look at this 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>(),  //  здесь все нормально
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // а здесь мы получаем предупреждение
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
The method takes a list and any number of objects addAll()as input , and then adds all these objects to the list. In the method we call our method twice . The first time we add in two regular lines. Everything is fine here. The second time we add in two objects . And here we suddenly get a warning: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
What does it mean? Why do we receive a warning and what does it have to do with it array? Array- this is an array, and there are no arrays in our code! Let's start with the second one. The warning mentions an array because the compiler converts variable length arguments (varargs) to an array. In other words, the signature of our method is addAll():
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 method main(), the compiler will convert 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")
        }
   );
}
Everything is fine with the array String. But with an array Pair<String, String>- no. The fact is that Pair<String, String>this is a Non-Reifiable Type. During compilation, all information about parameter types (<String, String>) will be erased. Creating arrays from Non-Reifiable Type is not allowed in Java . You can verify this if you try to manually create an array Pair<String, String>
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
The reason is obvious - type safety. As you remember, when creating an array, you must indicate which objects (or primitives) this array will store.
int array[] = new int[10];
In one of the previous lessons, we examined in detail the type erasing mechanism. So, in this case, as a result of erasing types, we lost the information that Pairpairs were stored in our objects <String, String>. Creating an array will be unsafe. When using methods with varargsand generics, be sure to remember about type erasure and exactly how it works. If you are absolutely confident in the code you have written, and you know that it will not cause any problems, you can disable varargswarnings associated with it using an annotation@SafeVarargs
@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 possible problem when using varargsgenerics together is heap pollution. Using varargs when working with generics - 4Contamination can occur in the following situations:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> makeHeapPollution() {
       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 = makeHeapPollution();

       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 a situation in which objects of type 1 should be on the heap А, but objects of type end up there B, due to type safety errors. In our example this is what happens. First we created a Raw variable numbersand assigned it a generic collection ArrayList<Number>. After that we added the number there 1.
List<String> strings = numbers;
In this line, the compiler tried to warn us about possible errors by issuing the warning “ Unchecked assignment... ”, but we ignored it. As a result, we have a generic variable of type List<String>, which points to a generic collection of type ArrayList<Number>. This situation can clearly lead to trouble! This is what happens. Using our new variable, we add a string to the collection. The heap was polluted - we added first a number and then a string to the typed collection. The compiler warned us, but we ignored its warning, receiving results ClassCastExceptiononly while the program was running. What does it have to do with it varargs? Use varargswith 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 makeHeapPollution(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");

       makeHeapPollution(cars1, cars2);
   }
}
What's going on here? Due to type erasure, our parameter sheets (we'll call them “sheets” instead of “lists” for convenience) are -
List<String>...stringsLists
- will turn into an array of sheets - List[]with an unknown type (do not forget that varargs turns into a regular array as a result of compilation). Because of this, we can easily make an assignment to a variable Object[] arrayin the first line of the method - the types have been erased from our sheets! And now we have a variable of type Object[], where we can add anything at all - all objects in Java inherit from Object! Right now we only have an array of string sheets. But thanks to the use varargsand erasure of types, we can easily add a sheet of numbers to them, which is what we do. As a result, we pollute the heap by mixing objects of different types. The result will be the same exception ClassCastExceptionwhen trying 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
These are the unexpected consequences that can result from using a seemingly simple mechanism varargs:) And this is where our lecture today comes to an end. Don’t forget to solve a couple of problems, and if you have time and energy left, study additional literature. “ Effective Java ” will not read itself! :) See you!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION