J'ai récemment rencontré l' @SafeVarargs
annotation java . Rechercher sur Google ce qui rend une fonction variadique dangereuse en Java m'a laissé plutôt confus (empoisonnement de tas? Types effacés?), Alors j'aimerais savoir quelques choses:
Qu'est-ce qui rend une fonction Java variadique dangereuse dans le
@SafeVarargs
sens du terme (de préférence expliqué sous la forme d'un exemple détaillé)?Pourquoi cette annotation est-elle laissée à la discrétion du programmeur? N'est-ce pas quelque chose que le compilateur devrait être capable de vérifier?
Y a-t-il une norme à laquelle il faut adhérer pour s'assurer que sa fonction est vraiment sans danger? Sinon, quelles sont les meilleures pratiques pour le garantir?
retType myMethod(Arg first, Arg... others)
. Si vous ne spécifiez pasfirst
, un tableau vide est autorisé, et vous pouvez très bien avoir une méthode du même nom avec le même type de retour ne prenant aucun argument, ce qui signifie que la JVM aura du mal à déterminer quelle méthode doit être appelée.Réponses:
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:
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 typeX[]
; 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 commenew 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 commeT...
, cela fonctionne également quandT
est connu pour être un type concret pour cet appel. Par exemple, si la méthode ci-dessus faisait partie d'une classeFoo<T>
, et que vous avez uneFoo<String>
référence, alors l'appelerfoo
serait acceptable car nous savons queT
c'estString
à 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 placenew Object[] { ... }
(voiciObject
la limite supérieure deT
; s'il y avait quelque chose de différent, ce serait celle-là au lieu deObject
), puis vous donne un avertissement du compilateur.Alors, qu'est-ce qui ne va pas avec la création
new Object[]
au lieu denew 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:
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:
Le problème ici est que nous dépendons du type d'
args
êtreT[]
pour le renvoyer sous la formeT[]
. Mais en fait, le type de l'argument à l'exécution n'est pas une instance deT[]
.3) Si votre méthode a un argument de type
T...
(où T est n'importe quel paramètre de type), alors:T
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 typeT[]
, obtenir le type de tableau à l'aide.getClass()
, le transmettre à des méthodes qui dépendent du type d'exécution du tableau, commeList.toArray()
etArrays.copyOf()
, etc.2) La distinction que j'ai mentionnée ci-dessus est trop compliquée pour être facilement distinguée automatiquement.
la source
FOO<T extends Something>
serait-il prudent de renvoyer le T [] d'une méthode varargs sous forme de tableau deSomthing
s?@SafeVarargs
est lorsque la seule chose que vous faites avec le tableau est de le passer à une autre méthode qui est déjà si annotée (par exemple: je me retrouve fréquemment à écrire des méthodes vararg qui existent pour convertir leur argument en liste en utilisantArrays.asList(...)
et le passer à une autre méthode; un tel cas peut toujours être annoté avec@SafeVarargs
car ilArrays.asList
contient l'annotation).@SafeVarargs
moins que la méthode ne soitstatic
oufinal
, car sinon, elle pourrait être remplacée dans une classe dérivée avec quelque chose de dangereux.private
méthodes qui ne peuvent pas non plus être remplacées.Pour les meilleures pratiques, considérez ceci.
Si vous avez ceci:
Changez-le en ceci:
J'ai constaté que je n'ajoute généralement que des varargs pour le rendre plus pratique pour mes appelants. Il serait presque toujours plus pratique pour mon implémentation interne d'utiliser un fichier
List<>
. Donc, pourArrays.asList()
me servir et m'assurer qu'il n'y a aucun moyen que je puisse introduire Heap Pollution, c'est ce que je fais.Je sais que cela ne répond qu'à votre # 3. newacct a donné une excellente réponse pour les # 1 et # 2 ci-dessus, et je n'ai pas assez de réputation pour laisser cela comme un commentaire. : P
la source