web-dev-qa-db-fra.com

Java SafeVarargs, existe-t-il une norme ou une pratique exemplaire?

Je suis récemment tombé sur l'annotation Java @ SafeVarargs. Googler pour ce qui rend une fonction variadique dans Java unsafe m'a laissé plutôt confus (empoisonnement du tas? types effacés?), alors j'aimerais savoir quelques choses:

  1. Qu'est-ce qui rend une fonction variadic Java dangereuse au sens @ SafeVarargs (de préférence expliquée sous la forme d'un exemple détaillé)?

  2. Pourquoi cette annotation est-elle laissée à la discrétion du programmeur? N'est-ce pas quelque chose que le compilateur devrait pouvoir vérifier?

  3. Existe-t-il une norme à respecter pour s'assurer que sa fonction est vraiment sécurisée? Si non, quelles sont les meilleures pratiques pour le garantir?

166
Oren

1) Il existe de nombreux exemples sur Internet et sur StackOverflow concernant le problème particulier lié aux génériques et aux variables. En gros, c'est quand vous avez un nombre variable d'arguments de type type-paramètre:

<T> void foo(T... args);

En Java, varargs est un sucre syntaxique qui subit une simple "réécriture" au moment de la compilation: un paramètre varargs de type X... Est converti en un paramètre de type X[]; et chaque fois qu'un appel est fait à cette méthode varargs, le compilateur collecte tous les "arguments variables" contenus dans le paramètre varargs et crée un tableau similaire à new X[] { ...(arguments go here)... }.

Cela fonctionne bien lorsque le type varargs est concret, comme String.... Quand il s'agit d'une variable de type comme T..., Cela fonctionne également lorsque T est connu pour être un type concret pour cet appel. par exemple. si la méthode ci-dessus faisait partie d'une classe Foo<T>, et que vous avez une référence Foo<String>, alors appeler foo dessus serait acceptable car nous savons que T est String à ce stade du code.

Cependant, cela ne fonctionne pas lorsque la "valeur" de T est un autre paramètre de type. En Java, il est impossible de créer un tableau d'un type de composant type-paramètre (new T[] { ... }). Donc Java utilise plutôt new Object[] { ... } (Ici Object est la limite supérieure de T; si la limite supérieure était différente, ce serait cela au lieu de Object), puis vous donne un avertissement pour le compilateur.

Alors, quel est le problème avec la création de new Object[] Au lieu de new T[] Ou quoi que ce soit? Eh bien, les tableaux dans Java connaissent leur type de composant au moment de l'exécution. Ainsi, l'objet tableau transmis aura un type de composant incorrect au moment de l'exécution.

Pour probablement l'utilisation la plus courante de varargs, simplement pour parcourir les éléments, ce n'est pas un problème (vous vous souciez du type d'exécution du tableau), c'est donc sûr:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

Cependant, tout ce qui dépend du type de composant d'exécution de la matrice transmise ne sera pas sûr. Voici un exemple simple de quelque chose qui est dangereux et se bloque:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

Le problème ici est que nous dépendons du type de args pour qu'il soit T[] Afin de le renvoyer sous la forme T[]. Mais en réalité, le type de l'argument au moment de l'exécution n'est pas une instance de T[].

3) Si votre méthode a un argument de type T... (Où T est un paramètre de type), alors:

  • Safe: Si votre méthode ne dépend que du fait que les éléments du tableau sont des instances de T
  • Unsafe: Si cela dépend du fait que le tableau est une instance de T[]

Les choses qui dépendent du type d’exécution du tableau incluent: le renvoyer sous le type T[], En le passant comme argument à un paramètre de type T[], Obtenant le type de tableau à l'aide de .getClass(), en le transmettant à des méthodes qui dépendent du type d’exécution du tableau, comme List.toArray() et Arrays.copyOf(), etc.

2) La distinction que j'ai mentionnée ci-dessus est trop compliquée pour pouvoir être facilement distinguée automatiquement.

228
newacct

Considérons les meilleures pratiques.

Si vous avez ceci:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Changez-le en ceci:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

J'ai constaté que j'ajoutais généralement uniquement des variantes pour améliorer le confort de mes interlocuteurs. Il serait presque toujours plus pratique que mon implémentation interne utilise un List<>. Donc, pour profiter de Arrays.asList() et m'assurer qu'il est impossible d'introduire la pollution en tas, voici ce que je fais.

Je sais que cela ne répond qu'à votre # 3. newacct a donné une excellente réponse pour les numéros 1 et 2 ci-dessus, et je n'ai pas assez de réputation pour laisser cela comme un commentaire. : P

0
Fridge