In der Vergangenheit habe ich gesagt, eine Sammlung sicher zu kopieren, um Folgendes zu tun:
public static void doThing(List<String> strs) {
List<String> newStrs = new ArrayList<>(strs);
oder
public static void doThing(NavigableSet<String> strs) {
NavigableSet<String> newStrs = new TreeSet<>(strs);
Aber sind diese "Kopier" -Konstruktoren, ähnliche statische Erstellungsmethoden und Streams, wirklich sicher und wo sind die Regeln angegeben? Mit sicher meine ich die grundlegenden semantischen Integritätsgarantien , die von der Java-Sprache und den Sammlungen angeboten werden, die gegen einen böswilligen Anrufer erzwungen werden, vorausgesetzt, sie werden von einem vernünftigen unterstützt SecurityManager
und es gibt keine Fehler.
Ich bin glücklich mit dem Verfahren werfen ConcurrentModificationException
, NullPointerException
, IllegalArgumentException
, ClassCastException
, usw., oder vielleicht sogar hängen.
Ich habe String
als Beispiel für ein unveränderliches Typargument gewählt. Für diese Frage interessieren mich keine tiefen Kopien für Sammlungen veränderlicher Typen, die ihre eigenen Fallstricke haben.
(Um klar zu sein, habe ich mir den OpenJDK-Quellcode angesehen und habe eine Antwort auf ArrayList
und TreeSet
.)
quelle
NavigableSet
und andereComparable
basierte Sammlungen können manchmal erkennen, ob eine Klasse nichtcompareTo()
korrekt implementiert ist , und eine Ausnahme auslösen. Es ist ein bisschen unklar, was Sie unter nicht vertrauenswürdigen Argumenten verstehen. Du meinst, ein Übeltäter bastelt eine Sammlung von schlechten Saiten und wenn du sie in deine Sammlung kopierst, passiert etwas Schlimmes? Nein, das Sammlungs-Framework ist ziemlich solide, es gibt es seit 1.2.HashSet
(und alle anderen Hashing-Sammlungen im Allgemeinen). Dies hängt von der Richtigkeit / Integrität derhashCode
Implementierung der Elemente abTreeSet
undPriorityQueue
hängt von der abComparator
(und Sie können nicht einmal Erstellen Sie eine äquivalente Kopie, ohne den benutzerdefinierten Komparator zu akzeptieren (falls vorhanden), undEnumSet
vertrauen Sie auf die Integrität des bestimmtenenum
Typs, der nach der Kompilierung nie überprüft wird, sodass eine Klassendatei, die nicht mitjavac
oder in Handarbeit erstellt wurde, diese untergraben kann.new TreeSet<>(strs)
wostrs
ist einNavigableSet
. Dies ist keine Massenkopie, da das ErgebnisTreeSet
den Komparator der Quelle verwendet, der sogar erforderlich ist, um die Semantik beizubehalten. Wenn Sie nur die enthaltenen Elemente verarbeiten können,toArray()
ist dies der richtige Weg. Es wird sogar die Iterationsreihenfolge beibehalten. Wenn Sie mit "Element nehmen, Element validieren, Element verwenden" einverstanden sind, müssen Sie nicht einmal eine Kopie erstellen. Die Probleme beginnen, wenn Sie alle Elemente überprüfen und anschließend alle Elemente verwenden möchten. Dann können Sie einerTreeSet
Kopie mit benutzerdefiniertem Komparator nicht vertrauencheckcast
für jedes Element erfolgttoArray
mit einem bestimmten Typ. Wir enden immer damit. Die generischen Sammlungen kennen nicht einmal ihren tatsächlichen Elementtyp, sodass ihre Kopierkonstruktoren keine ähnliche Funktionalität bereitstellen können. Natürlich können Sie jede Überprüfung auf die richtige vorherige Verwendung verschieben, aber dann weiß ich nicht, worauf Ihre Fragen abzielen. Sie benötigen keine "semantische Integrität", wenn Sie unmittelbar vor der Verwendung von Elementen prüfen und fehlschlagen können.Antworten:
Es gibt keinen wirklichen Schutz vor absichtlich bösartigem Code, der in gewöhnlichen APIs wie der Collection-API in derselben JVM ausgeführt wird.
Wie leicht gezeigt werden kann:
Wie Sie sehen können,
List<String>
garantiert das Erwarten von a nicht, dass tatsächlich eine Liste vonString
Instanzen angezeigt wird. Aufgrund des Löschens von Typen und der Rohtypen ist auf der Seite der Listenimplementierung nicht einmal eine Korrektur möglich.Die andere Sache, für die Sie den
ArrayList
Konstruktor verantwortlich machen können, ist das Vertrauen in dietoArray
Implementierung der eingehenden Sammlung .TreeMap
wird nicht auf die gleiche Weise beeinflusst, sondern nur, weil es keinen solchen Leistungsgewinn durch das Übergeben des Arrays gibt, wie bei der Konstruktion einesArrayList
. Keine der Klassen garantiert einen Schutz im Konstruktor.Normalerweise macht es keinen Sinn, Code zu schreiben, der absichtlich bösartigen Code an jeder Ecke voraussetzt. Es kann zu viel tun, um sich vor allem zu schützen. Ein solcher Schutz ist nur für Code nützlich, der eine Aktion wirklich kapselt, die einem böswilligen Anrufer Zugriff auf etwas gewähren könnte. Ohne diesen Code könnte er nicht bereits darauf zugreifen.
Wenn Sie Sicherheit für einen bestimmten Code benötigen, verwenden Sie
Dann können Sie sicher sein, dass
newStrs
das nur Zeichenfolgen enthält und nach seiner Erstellung nicht durch anderen Code geändert werden kann.Oder
List<String> newStrs = List.of(strs.toArray(new String[0]));
mit Java 9 oder neuer verwendenBeachten Sie, dass Java 10
List.copyOf(strs)
dasselbe tut, in der Dokumentation jedoch nicht angegeben ist, dass dietoArray
Methode der eingehenden Sammlung garantiert nicht vertrauenswürdig ist . Ein AufrufList.of(…)
, der definitiv eine Kopie erstellt, falls eine Array-basierte Liste zurückgegeben wird, ist also sicherer.Da kein Aufrufer die Art und Weise ändern kann, funktionieren Arrays. Wenn Sie die eingehende Sammlung in ein Array kopieren und anschließend die neue Sammlung damit füllen, wird die Kopie immer sicher. Da die Auflistung einen Verweis auf das zurückgegebene Array enthalten kann, wie oben gezeigt, kann sie ihn während der Kopierphase ändern, die Kopie in der Auflistung kann jedoch nicht beeinflusst werden.
Daher sollten Konsistenzprüfungen durchgeführt werden, nachdem das bestimmte Element aus dem Array oder der resultierenden Sammlung als Ganzes abgerufen wurde.
quelle
AccessController.doPrivileged(…)
usw. ausführen möchten . Die lange Liste der Fehler im Zusammenhang mit der Applet-Sicherheit gibt uns jedoch einen Hinweis darauf, warum diese Technologie aufgegeben wurde…new ArrayList<>(…)
als Kopierkonstruktor ist unter der Annahme korrekter Sammlungsimplementierungen in Ordnung. Es ist nicht Ihre Pflicht, Sicherheitsprobleme zu beheben, wenn es bereits zu spät ist. Was ist mit kompromittierter Hardware? Das Betriebssystem? Wie wäre es mit Multithreading?List.copyOf(strs)
verlässt sich diesbezüglich nicht auf die Richtigkeit der eingehenden Sammlung zum offensichtlichen Preis.ArrayList
ist ein alltäglicher Kompromiss.toArray()
selbst aufrufen , da Arrays kein überschriebenes Verhalten aufweisen können, und anschließend eine Sammlungskopie des Arrays wienew ArrayList<>(Arrays.asList( strs.toArray(new String[0])))
oder erstellenList.of(strs.toArray(new String[0]))
. Beide haben auch den Nebeneffekt, den Elementtyp zu erzwingen. Ich persönlich glaube nicht, dass sie jemals zulassen werdencopyOf
, die unveränderlichen Sammlungen zu kompromittieren, aber die Alternativen sind in der Antwort enthalten.Ich würde diese Informationen lieber im Kommentar hinterlassen, aber ich habe nicht genug Ruf, sorry :) Ich werde versuchen, sie so ausführlich wie möglich zu erklären.
Anstelle eines
const
Modifikators, der in C ++ zum Markieren von Elementfunktionen verwendet wird, die den Objektinhalt nicht ändern sollen, wurde in Java ursprünglich das Konzept der "Unveränderlichkeit" verwendet. Die Einkapselung (oder OCP, Open-Closed-Prinzip) sollte vor unerwarteten Mutationen (Änderungen) eines Objekts schützen. Natürlich geht die Reflection API darum herum; Der direkte Speicherzugriff macht dasselbe. das ist mehr über das eigene Bein schießen :)java.util.Collection
selbst ist eine veränderbare Schnittstelle: Es hat eineadd
Methode, die die Sammlung ändern soll. Natürlich kann der Programmierer die Sammlung in etwas einwickeln, das auslöst ... und alle Laufzeitausnahmen werden auftreten, weil ein anderer Programmierer Javadoc nicht lesen konnte, was eindeutig besagt, dass die Sammlung unveränderlich ist.Ich habe mich für
java.util.Iterable
type entschieden, um unveränderliche Sammlungen in meinen Schnittstellen verfügbar zu machen. SemantischIterable
hat keine Sammlungseigenschaft wie "Veränderlichkeit". Dennoch können Sie (höchstwahrscheinlich) zugrunde liegende Sammlungen über Streams ändern.JIC, um Karten unveränderlich freizulegen,
java.util.Function<K,V>
kann verwendet werden (dieget
Methode der Karte entspricht dieser Definition).quelle
Iterator
, erzwingt dies praktisch eine elementweise Kopie, aber das ist nicht schön. Die Verwendung vonforEachRemaining
/forEach
wird offensichtlich eine völlige Katastrophe sein. (Ich muss auch erwähnen, dass diesIterator
eineremove
Methode hat.)Iterable
, dass es nicht wirklich unveränderlich ist, sehe aber keine Probleme mitforEach*
)