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

Using varargs when dealing 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 is this word - "Reifiable"? Even if everything is fine with English, you hardly met him. Let's try to translate! Using varargs when dealing with generics - 2
*thank you google, you helped a lot -_-*
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 is ?

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 sum. sum()You can pass 2 numbers, 3, 5, or as many as you like into 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, the 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 accepts 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. It's all right here. The second time we add in two . 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 get a warning and what does it have to do with it array? Arrayis an array, and there are no arrays in our code! Let's start with the second. The warning mentions an array because the compiler converts variable length arguments (varargs) to an array. In other words, our method signature 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 to 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")
        }
   );
}
With an array, Stringeverything is fine. But with an array Pair<String, String>- no. The matter is that Pair<String, String>is Non-Reifiable Type. When compiling, all information about parameter types (<String, String>) will be erased. Creating arrays from a Non-Reifiable Type is not allowed in Java . You can see this if you try to manually create an array of 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 specify which objects (or primitives) this array will store.
int array[] = new int[10];
In one of the previous lessons, we discussed in detail the type erasure mechanism. So, in this case, as a result of type erasure, we lost information that Pairpairs were stored in our objects <String, String>. Creating an array would be unsafe. Using varargs when dealing with generics - 3When using methods with varargsand generics, be sure to keep type erasure in mind and how exactly 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 related varargswarnings using the 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 with sharing varargsand generics is heap pollution. Using varargs when dealing with generics - 4Contamination can occur in the following situation:
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 there should be objects of type on the heap А, but as a result there are objects of type B- due to type safety errors. In our example, this is what happens. We first created a Raw variable numbersand assigned a generic collection to it ArrayList<Number>. After that, we added a number there 1.
List<String> strings = numbers;
In this line, the compiler tried to warn us of 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 obviously lead to trouble! And so it happens. Using our new variable, we add a string to the collection. Heap pollution has occurred - we added a number to the typed collection first, and then a string. The compiler warned us, but we ignored its warning, getting the result ClassCastExceptiononly while the program was running. And what about here varargs? Using varargsgenerics can easily lead to heap pollution. Here is 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 (let's call them "sheets" instead of "lists" for convenience) are
List<String>...stringsLists
- 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 you can add anything at all - all objects in Java are inherited from Object! Now we only have an array of string sheets. But through the use varargsand erasure of types, we can easily add a sheet of numbers to them, which we do. As a result, we pollute the heap by mixing objects of different types. The result will still be the same exception ClassCastExceptionwhen trying to read a string from an 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 the use of a seemingly simple mechanism varargs:) And our today's lecture is coming to an end. Do not forget to solve a couple of problems, and if there is time and energy left, study additional literature. Effective Java won't read itself! :) See you!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION