[JAVA] Point 32: Combiner judicieusement les génériques et les varargs

32. Soyez prudent lorsque vous associez des génériques à des arguments de longueur variable

Les méthodes et les génériques avec des arguments de longueur variable ont été publiés simultanément dans Java 5, mais ils ne vont pas bien ensemble. L'argument de longueur variable est une abstraction qui fuit. https://euske.github.io/slides/sem20170627/index.html Lors de l'appel d'une méthode avec des arguments de longueur variable, un tableau est créé pour contenir les arguments de longueur variable, et ce tableau est visible. (Lorsqu'elle est abstraite, elle semble être appelée abstraction qui fuit, ce qui signifie que l'utilisateur ne doit pas en être conscient.) En conséquence, le compilateur est confus lors du passage de types génériques ou paramétrés à des arguments de longueur variable. Si vous déclarez une méthode avec un type non réifiable comme argument de longueur variable, le compilateur lève un avertissement. L'avertissement est également déclenché lorsqu'une méthode est appelée à laquelle est passé un argument de longueur variable avec un type qui est supposé non réifiable. L'avertissement est le suivant.

warning: [unchecked] Possible heap pollution from
    parameterized vararg type List<String>

La pollution du tas se produit lorsque divers types paramétrés font référence à un type différent. La pollution du tas provoque l'échec de la conversion automatique du compilateur, violant la base de type sécurisé garantie par les types génériques.

À titre d'exemple, considérons la méthode suivante.

// Mixing generics and varargs can violate type safety!
static void dangerous(List<String>... stringLists) {
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    objects[0] = intList;             // Heap pollution
    String s = stringLists[0].get(0); // ClassCastException
}

Cette méthode n'a pas de distribution claire, mais j'obtiens un ClassCastException ''. Un cast implicite par le compilateur a été effectué sur la dernière ligne, échouant. Cela indique qu'il est ** dangereux de stocker des valeurs dans des arguments génériques de longueur variable **. La raison pour laquelle une erreur de compilation ne se produit pas lorsqu'une méthode avec un argument de longueur variable d'un type générique est déclarée est que l'argument de longueur variable d'un type générique ou d'un type paramétré est très pratique. Par exemple, ```Arrays.asList (T ... a) , Collections.addAll (Collection <? Super T> c, T ... éléments) `,` `ʻEnumSet. Il existe des méthodes dans la bibliothèque standard Java telles que of (E first, E ... rest), qui sont de type sécurisé. Avant Java6, l'auteur de la méthode n'avait aucun moyen de gérer l'avertissement apparaissant lors de l'appel d'une méthode avec un argument de longueur variable générique. Cela signifiait que l'appelant devait écrire @ SuppressWarnings ("non coché") à chaque fois. Dans Java7, l'annotation SafeVarargs '' a été ajoutée. En donnant ceci à une méthode avec un argument de longueur variable de type générique, l'appelant n'obtiendra pas d'avertissement. ** L'annotation SafeVarargs '' indique que l'auteur de la méthode promet que la méthode est de type sûr. ** ** safevarargsDans les annotations, il est vraiment important d'être sûr de type, mais comment devrions-nous être sûr qu'il est de type sûr? Les tableaux de type générique sont générés lorsqu'une méthode est appelée pour contenir un tableau de longueur variable. Dans le traitement d'une méthode, il est sûr (c'est-à-dire uniquement un simple transfert de l'argument vers la méthode) s'il ne stocke pas dans le tableau et n'autorise pas les références du code non approuvé au tableau. On peut dire que c'est sûr). Il convient de noter que même si rien n'est stocké pour un tableau d'arguments de longueur variable, cela peut menacer la sécurité du type. L'exemple suivant semble correct, mais il est dangereux.

// UNSAFE - Exposes a reference to its generic parameter array!
static <T> T[] toArray(T... args) {
    return args;
}

Le type de ce tableau dépend du type à la compilation du type passé dans l'argument de la méthode, mais le compilateur ne donne pas suffisamment d'informations pour prendre une décision précise. Cette méthode renvoie un tableau d'arguments de longueur variable, de sorte qu'une pollution de tas se produit dans la pile d'appels. Pour penser concrètement, considérez la méthode suivante.

static <T> T[] pickTwo(T a, T b, T c) {
    switch(ThreadLocalRandom.current().nextInt(3)) {
      case 0: return toArray(a, b);
      case 1: return toArray(a, c);
      case 2: return toArray(b, c);
    }
    throw new AssertionError(); // Can't get here
}

En compilant cette méthode, le compilateur produit du code qui produit un tableau d'arguments de longueur variable pour passer deux instances T à la méthode `` `` toArray. Le code place un tableau de Object [] `` `afin que n'importe quel objet puisse être passé par l'appelant. La méthode `` `` toArray renvoie simplement ce tableau à la méthode pickTwo, et la méthode `` `` pickTwo renvoie ce tableau à l'appelant. Par conséquent, la méthode `pickTwo``` renvoie toujours un tableau de type```Object [] `. Pensez à appeler `` pickTwo '' à partir du code principal ci-dessous.

public static void main(String[] args) {
    String[] attributes = pickTwo("Good", "Fast", "Cheap");
}

Concernant ce code, ni une erreur de compilation ni un avertissement n'apparaissent, mais lorsque je l'exécute, j'obtiens une ClassCastException '' même si je ne l'ai pas explicitement castée. Cela se produit lorsque j'essaye de le rendre String [] parce que le type de retour de la méthode pickTwo``` est ʻObject [] ``. .. Cet exemple nous rappelle qu'il n'est pas sûr d'autoriser d'autres méthodes à accéder à un tableau d'arguments de longueur variable générique **. Il y a deux exceptions à cela:

Voici comment utiliser un argument générique de longueur variable sécurisé typique:

// Safe method with a generic varargs parameter
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
    List<T> result = new ArrayList<>();
    for (List<? extends T> list : lists)
        result.addAll(list);
    return result;
}

safevarargsLa décision d'ajouter une annotation est simple,Pour toutes les méthodes avec des arguments de longueur variable de types génériques ou paramétréssafevarargsDoit être annoté.. Cela signifie quetoarrayN'écrivez pas de méthodes d'argument de longueur variable non sécurisées comme les méthodes. La méthode de l'argument de longueur variable sûre satisfait:

  1. Rien n'est stocké dans l'argument de longueur variable
  2. Ne rendez pas les arguments de longueur variable visibles pour du code non fiable

De plus, l'annotation `` SafeVarargs '' n'est valide que pour les méthodes qui ne sont pas remplacées.

safevarargsEn tant que correspondance autre que l'utilisation d'annotations, à partir de l'élément 28, arguments de longueur variablelistIl est envisageable de le remplacer par. PuisflattenLa méthode change comme suit.

// List as a typesafe alternative to a generic varargs parameter
static <T> List<T> flatten(List<List<? extends T>> lists) {
    List<T> result = new ArrayList<>();
    for (List<? extends T> list : lists)
        result.addAll(list);
    return result;
}

La bonne chose à ce sujet est que cela garantit la sécurité du type et que vous n'avez pas à ajouter vous-même l'annotation `` SafeVarargs ''. La mauvaise nouvelle est que le code côté client peut être un peu fastidieux et un peu plus lent.

Recommended Posts

Point 32: Combiner judicieusement les génériques et les varargs
Point 53: Utilisez judicieusement les varargs
Point 67: Optimiser judicieusement
Article 55: Renvoyez judicieusement les options
Item 52: Utiliser la surcharge judicieusement
Point 45: Utilisez judicieusement les flux
Point 83: Utilisez judicieusement l'initialisation paresseuse
[Java] Classe générique et méthode générique
Point 66: Utiliser judicieusement les méthodes natives
URLSession avec URLSession et Combine normalement
java Generics T et? Différence