1) Il existe de nombreux exemples sur Internet et sur StackOverflow concernant le problème particulier des génériques et des varargs. En gros, c'est quand vous avez un nombre variable d'arguments d'un type paramètre de type:
<T> void foo(T... args);
En Java, les varargs sont un sucre syntaxique qui subit une simple "réécriture" à 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" qui vont dans le paramètre varargs, et crée un tableau comme new X[] { ...(arguments go here)... }
.
Cela fonctionne bien lorsque le type varargs est comme du béton String...
. Quand il s'agit d'une variable de type comme T...
, cela fonctionne également quand 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 Foo<String>
référence, alors l'appeler foo
serait acceptable car nous savons que T
c'est String
à ce point dans le 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 composant de type paramètre de type ( new T[] { ... }
). Donc, Java utilise à la place new Object[] { ... }
(voici Object
la limite supérieure de T
; s'il y avait quelque chose de différent, ce serait celle-là au lieu de Object
), puis vous donne un avertissement du compilateur.
Alors, qu'est-ce qui ne va pas avec la création new Object[]
au lieu de new T[]
ou quoi que ce soit? Eh bien, les tableaux en Java connaissent leur type de composant au moment de l'exécution. Ainsi, l'objet tableau transmis aura le mauvais type de composant lors 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 ne vous souciez pas du type d'exécution du tableau), donc c'est sûr:
@SafeVarargs
final <T> void foo(T... args) {
for (T x : args) {
// do stuff with x
}
}
Cependant, pour tout ce qui dépend du type de composant d'exécution du tableau passé, ce ne sera pas sûr. Voici un exemple simple de quelque chose qui n'est pas sûr et qui plante:
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 d' args
être T[]
pour le renvoyer sous la forme T[]
. Mais en fait, le type de l'argument à l'exécution n'est pas une instance de T[]
.
3) Si votre méthode a un argument de type T...
(où T est n'importe quel paramètre de type), alors:
- Sûr: Si votre méthode dépend uniquement 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 en tant que type T[]
, le passer en tant qu'argument à un paramètre de type T[]
, obtenir le type de tableau à l'aide .getClass()
, le transmettre à 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 être facilement distinguée automatiquement.