Salut! Dans la leçon d'aujourd'hui, nous allons continuer à étudier les génériques. Il se trouve que c'est un sujet important, mais il est impossible de l'éviter - c'est une partie extrêmement importante du langage :) Lorsque vous étudiez la documentation Oracle sur les génériques ou lisez des didacticiels en ligne, vous rencontrerez les termes types non réifiables et types réifiables . Un type réifiable est un type pour lequel les informations sont entièrement disponibles au moment de l'exécution. En Java, ces types incluent les primitives, les types bruts et les types non génériques. En revanche, les types non réifiables sont des types dont les informations sont effacées et deviennent inaccessibles au moment de l'exécution. En l'occurrence, ce sont des génériques - List<String>, List<Integer>, etc.

Au fait, vous souvenez-vous de ce qu'est varargs ?

Au cas où vous l'auriez oublié, il s'agit d'un argument de longueur variable. Ils sont utiles dans les situations où nous ne savons pas combien d'arguments peuvent être passés à notre méthode. Par exemple, si nous avons une classe de calculatrice qui a une summéthode. La sum()méthode peut recevoir 2 numéros, ou 3, ou 5, ou autant que vous le souhaitez. Il serait très étrange de surcharger la sum()méthode pour chaque nombre possible d'arguments. Au lieu de cela, nous pouvons faire ceci :

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));
   }
}
Sortie console :

15
11
Cela nous montre qu'il existe des fonctionnalités importantes lors de l'utilisation de varargs en combinaison avec des génériques. Regardons le code suivant :

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")
       );
   }
}
La addAll()méthode prend en entrée a List<E>et n'importe quel nombre d' Eobjets, puis elle ajoute tous ces objets à la liste. Dans la main()méthode, nous appelons notre addAll()méthode deux fois. Dans le premier cas, nous ajoutons deux chaînes ordinaires au List. Tout est en ordre ici. Dans le second cas, nous ajoutons deux Pair<String, String>objets au fichier List. Mais ici, nous recevons de manière inattendue un avertissement :

Unchecked generics array creation for varargs parameter
Qu'est-ce que cela signifie? Pourquoi recevons-nous un avertissement et pourquoi y a-t-il mention d'un array? Après tout, notre code n'a pas de array! Commençons par le deuxième cas. L'avertissement mentionne un tableau car le compilateur convertit l'argument de longueur variable (varargs) en tableau. Autrement dit, la signature de notre addAll()méthode est :

public static <E> void addAll(List<E> list, E... array)
Il ressemble en fait à ceci :

public static <E> void addAll(List<E> list, E[] array)
Autrement dit, dans la main()méthode, le compilateur convertit notre code en ceci :

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") 
        } 
   ); 
}
Un Stringtableau est très bien. Mais un Pair<String, String>tableau ne l'est pas. Le problème est qu'il Pair<String, String>s'agit d'un type non réifiable. Lors de la compilation, toutes les informations sur les arguments de type (<String, String>) sont effacées. La création de tableaux d'un type non réifiable n'est pas autorisée en Java . Vous pouvez le voir si vous essayez de créer manuellement un tableau Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
La raison est évidente : la sécurité de type. Comme vous vous en souviendrez, lors de la création d'un tableau, vous devez absolument spécifier quels objets (ou primitives) le tableau stockera.

int array[] = new int[10];
Dans l'une de nos leçons précédentes, nous avons examiné en détail l'effacement de type. Dans ce cas, l'effacement de type nous fait perdre les informations que les Pairobjets stockent <String, String>par paires. La création du tableau serait dangereuse. Lorsque vous utilisez des méthodes qui impliquent des varargs et des génériques, n'oubliez pas de vous souvenir de l'effacement de type et de son fonctionnement. Si vous êtes absolument certain du code que vous avez écrit et que vous savez qu'il ne causera aucun problème, vous pouvez désactiver les avertissements liés à varargs à l'aide des @SafeVarargsannotations.

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

   for (E element : array) {
       list.add(element);
   }
}
Si vous ajoutez cette annotation à votre méthode, l'avertissement que nous avons rencontré précédemment n'apparaîtra pas. Un autre problème qui peut survenir lors de l'utilisation de varargs avec des génériques est la pollution du tas. Utilisation de varargs lorsque vous travaillez avec des génériques - 3La pollution en tas peut se produire dans la situation suivante :

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));
   }
}
Sortie console :

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
En termes simples, la pollution du tas se produit lorsque les objets de type Adevraient être dans le tas, mais que les objets de type Bs'y retrouvent en raison d'erreurs liées à la sécurité des types. Dans notre exemple, c'est précisément ce qui se passe. Tout d'abord, nous avons créé la numbersvariable brute et ArrayList<Number>lui avons attribué une collection générique ( ). Ensuite, nous avons ajouté le numéro 1à la collection.

List<String> strings = numbers;
Sur cette ligne, le compilateur a tenté de nous avertir d'éventuelles erreurs en émettant l'avertissement " Unchecked Assignment... ", mais nous l'avons ignoré. Nous nous retrouvons avec une variable générique de type List<String>qui pointe vers une collection générique de type ArrayList<Number>. De toute évidence, cette situation peut entraîner des ennuis! Et c'est ainsi. En utilisant notre nouvelle variable, nous ajoutons une chaîne à la collection. Nous avons maintenant une pollution en tas - nous avons ajouté un nombre, puis une chaîne à la collection paramétrée. Le compilateur nous a prévenus, mais nous avons ignoré son avertissement. En conséquence, nous obtenons un ClassCastExceptionuniquement pendant que le programme est en cours d'exécution. Alors qu'est-ce que cela a à voir avec les varargs ? L'utilisation de varargs avec des génériques peut facilement entraîner une pollution du tas. Voici un exemple simple :

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);
   }
}
Que se passe t-il ici? En raison de l'effacement de type, notre argument de longueur variable

List<String>...stringsLists
devient un tableau de listes, c'est-à-dire List[]d'objets de type inconnu (n'oubliez pas que varargs se transforme en un tableau normal lors de la compilation). De ce fait, nous pouvons facilement l'affecter à la Object[] arrayvariable de la première ligne de la méthode — le type des objets de nos listes a été effacé ! Et maintenant nous avons une Object[]variable, à laquelle nous pouvons ajouter n'importe quoi, puisque tous les objets en Java héritent Object! Au début, nous n'avons qu'un tableau de listes de chaînes. Mais grâce à l'effacement de type et à notre utilisation des varargs, nous pouvons facilement ajouter une liste de nombres, ce que nous faisons. En conséquence, nous polluons le tas en mélangeant des objets de différents types. Le résultat en sera encore un autre ClassCastExceptionlorsque nous essaierons de lire une chaîne du tableau. Sortie console :

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
De telles conséquences inattendues peuvent être causées par l'utilisation de varargs, un mécanisme apparemment simple :) Et avec cela, la leçon d'aujourd'hui se termine. N'oubliez pas de résoudre quelques tâches, et si vous avez du temps et de l'énergie, étudiez quelques lectures supplémentaires. " Java efficace " ne se lit pas tout seul ! :) Jusqu'à la prochaine fois!