Dans le passé, j'ai dit que pour copier une collection en toute sécurité, faites quelque chose comme:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
ou
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
Mais ces constructeurs de «copie», des méthodes de création statique similaires et des flux, sont-ils vraiment sûrs et où sont les règles spécifiées? Par sûr, j'entends les garanties d' intégrité sémantique de base offertes par le langage Java et les collections appliquées contre un appelant malveillant, en supposant SecurityManager
qu'elles soient sauvegardées par un raisonnable et qu'il n'y a pas de défauts.
Je suis heureux avec le lancement de la méthode ConcurrentModificationException
, NullPointerException
, IllegalArgumentException
, ClassCastException
, etc., ou peut - être même accroché.
J'ai choisi String
comme exemple d'argument de type immuable. Pour cette question, je ne suis pas intéressé par les copies complètes pour les collections de types mutables qui ont leurs propres pièges.
(Pour être clair, j'ai regardé le code source d'OpenJDK et j'ai une sorte de réponse pour ArrayList
et TreeSet
.)
la source
NavigableSet
et d'autresComparable
collections basées peuvent parfois détecter si une classe ne s'implémente pascompareTo()
correctement et lever une exception. On ne sait pas trop ce que vous entendez par arguments non fiables. Vous voulez dire qu'un malfaiteur crée une collection de mauvaises cordes et lorsque vous les copiez dans votre collection, quelque chose de mauvais se produit? Non, le cadre des collections est assez solide, il existe depuis la 1.2.HashSet
(et toutes les autres collections de hachage en général) repose sur l'exactitude / l'intégrité de lahashCode
mise en œuvre des éléments,TreeSet
etPriorityQueue
dépend de laComparator
(et vous ne pouvez même pas créer une copie équivalente sans accepter le comparateur personnalisé s'il en existe un),EnumSet
fait confiance à l'intégrité duenum
type particulier qui n'est jamais vérifié après la compilation, de sorte qu'un fichier de classe, non généréjavac
ou fabriqué à la main, peut le subvertir.new TreeSet<>(strs)
oùstrs
est unNavigableSet
. Ce n'est pas une copie en bloc, car le résultatTreeSet
utilisera le comparateur de la source, ce qui est même nécessaire pour conserver la sémantique. Si vous êtes prêt à simplement traiter les éléments contenus,toArray()
c'est la voie à suivre; il conservera même l'ordre d'itération. Lorsque vous êtes d'accord avec "prendre élément, valider élément, utiliser élément", vous n'avez même pas besoin d'en faire une copie. Les problèmes commencent lorsque vous souhaitez vérifier tous les éléments, puis en utilisant tous les éléments. Ensuite, vous ne pouvez pas faire confiance à uneTreeSet
copie avec un comparateur personnalisécheckcast
pour chaque élément, concernetoArray
un type spécifique. Nous y terminons toujours. Les collections génériques ne connaissent même pas leur type d'élément réel, de sorte que leurs constructeurs de copie ne peuvent pas fournir une fonctionnalité similaire. Bien sûr, vous pouvez reporter tout contrôle à une bonne utilisation antérieure, mais je ne sais pas à quoi vos questions visent. Vous n'avez pas besoin d '«intégrité sémantique», lorsque vous êtes prêt à vérifier et à échouer immédiatement avant d'utiliser des éléments.Réponses:
Il n'y a pas de véritable protection contre le code intentionnellement malveillant exécuté dans la même JVM dans les API ordinaires, comme l'API Collection.
Comme on peut facilement le démontrer:
Comme vous pouvez le voir, s'attendre à ce qu'une
List<String>
garantie n'obtienne pas réellement une liste d'String
instances. En raison de l'effacement des types et des types bruts, il n'y a même pas de correctif possible du côté de l'implémentation de la liste.L'autre chose dont vous pouvez blâmer
ArrayList
le constructeur est la confiance dans l'toArray
implémentation de la collection entrante .TreeMap
n'est pas affecté de la même manière, mais uniquement parce qu'il n'y a pas un tel gain de performances en passant le tableau, comme dans la construction d'unArrayList
. Aucune des deux classes ne garantit une protection dans le constructeur.Normalement, il est inutile d'essayer d'écrire du code en supposant du code intentionnellement malveillant à chaque coin de rue. Il y a trop à faire pour se protéger de tout. Une telle protection n'est utile que pour le code qui encapsule vraiment une action qui pourrait donner à un appelant malveillant l'accès à quelque chose, il ne pourrait pas déjà y accéder sans ce code.
Si vous avez besoin de sécurité pour un code particulier, utilisez
Ensuite, vous pouvez être sûr qu'il
newStrs
ne contient que des chaînes et ne peut pas être modifié par un autre code après sa construction.Ou utilisez
List<String> newStrs = List.of(strs.toArray(new String[0]));
avec Java 9 ou une version plus récente.Notez que Java 10
List.copyOf(strs)
fait de même, mais sa documentation n'indique pas qu'il est garanti de ne pas faire confiance à latoArray
méthode de la collection entrante . Donc, appelerList.of(…)
, qui fera certainement une copie au cas où il retournerait une liste basée sur un tableau, est plus sûr.Étant donné qu'aucun appelant ne peut modifier la façon dont les tableaux fonctionnent, le vidage de la collection entrante dans un tableau, suivi du remplissage de la nouvelle collection avec elle, rendra toujours la copie sûre. Étant donné que la collection peut contenir une référence au tableau renvoyé, comme illustré ci-dessus, elle peut la modifier pendant la phase de copie, mais elle ne peut pas affecter la copie dans la collection.
Par conséquent, tout contrôle de cohérence doit être effectué après que l'élément particulier a été récupéré du tableau ou sur la collection résultante dans son ensemble.
la source
AccessController.doPrivileged(…)
etc. Mais la longue liste de bogues liés à la sécurité des applets nous donne une idée de la raison pour laquelle cette technologie a été abandonnée…new ArrayList<>(…)
tant que constructeur de copie est acceptable en supposant des implémentations de collection correctes. Ce n'est pas votre devoir de résoudre les problèmes de sécurité quand il est déjà trop tard. Qu'en est-il du matériel compromis? Le système d'exploitation? Que diriez-vous du multi-threading?List.copyOf(strs)
ne repose pas sur l'exactitude de la collecte entrante à cet égard, au prix évident.ArrayList
est un compromis raisonnable pour tous les jours.toArray()
, car les tableaux ne peuvent pas avoir de comportement remplacé, puis créer une copie de collection du tableau, commenew ArrayList<>(Arrays.asList( strs.toArray(new String[0])))
ouList.of(strs.toArray(new String[0]))
. Les deux ont également pour effet secondaire d'imposer le type d'élément. Personnellement, je ne pense pas qu'ils permettront jamaiscopyOf
de compromettre les collections immuables, mais les alternatives sont là, dans la réponse.Je préfère laisser ces informations en commentaire, mais je n'ai pas assez de réputation, désolé :) Je vais essayer de l'expliquer le plus verbeux possible.
Au lieu de quelque chose comme un
const
modificateur utilisé en C ++ pour marquer les fonctions membres qui ne sont pas censées modifier le contenu des objets, en Java était à l'origine utilisé le concept d '"immuabilité". L'encapsulation (ou OCP, Open-Closed Principle) était censée protéger contre toute mutation (modification) inattendue d'un objet. Bien sûr, l'API de réflexion marche autour de cela; l'accès direct à la mémoire fait de même; c'est plus de tirer sur sa propre jambe :)java.util.Collection
lui-même est une interface mutable: il a uneadd
méthode qui est censée modifier la collection. Bien sûr, le programmeur peut envelopper la collection dans quelque chose qui déclenchera ... et toutes les exceptions d'exécution se produiront car un autre programmeur n'a pas pu lire javadoc qui indique clairement que la collection est immuable.J'ai décidé d'utiliser le
java.util.Iterable
type pour exposer la collection immuable dans mes interfaces. SémantiquementIterable
n'a pas une telle caractéristique de collection que la "mutabilité". Vous pourrez toujours (très probablement) modifier les collections sous-jacentes via des flux.JIC, pour exposer des cartes de manière immuable
java.util.Function<K,V>
peut être utilisé (laget
méthode de la carte correspond à cette définition)la source
Iterator
alors cela force pratiquement une copie élément par élément, mais ce n'est pas bien. L'utilisation deforEachRemaining
/forEach
va évidemment être un désastre complet. (Je dois également mentionner que celaIterator
a uneremove
méthode.)Iterable
le fait que ce n'est pas réellement immuable, mais je ne vois aucun problème avecforEach*
)