Warum enthält Java 8 keine unveränderlichen Sammlungen?

130

Das Java-Team hat eine Menge großartiger Arbeit geleistet, um Hindernisse für die funktionale Programmierung in Java 8 zu beseitigen. Insbesondere die Änderungen an den java.util-Sammlungen ermöglichen die Verkettung von Transformationen in sehr schnelle Streaming-Vorgänge. In Anbetracht der guten Arbeit, die sie beim Hinzufügen erstklassiger Funktionen und funktionaler Methoden zu Sammlungen geleistet haben, warum konnten sie überhaupt keine unveränderlichen Sammlungen oder sogar unveränderlichen Sammlungsschnittstellen bereitstellen?

Ohne den vorhandenen Code zu ändern, kann das Java-Team jederzeit unveränderbare Schnittstellen hinzufügen, die mit den veränderlichen identisch sind, abzüglich der "set" -Methoden, und die vorhandenen Schnittstellen so erweitern, dass sie davon abweichen:

                  ImmutableIterable
     ____________/       |
    /                    |
Iterable        ImmutableCollection
   |    _______/    /          \   \___________
   |   /           /            \              \
 Collection  ImmutableList  ImmutableSet  ImmutableMap  ...
    \  \  \_________|______________|__________   |
     \  \___________|____________  |          \  |
      \___________  |            \ |           \ |
                  List            Set           Map ...

Sicher, Operationen wie List.add () und Map.put () geben derzeit einen booleschen oder vorherigen Wert für den angegebenen Schlüssel zurück, um anzuzeigen, ob die Operation erfolgreich war oder fehlgeschlagen ist. Unveränderliche Sammlungen müssten solche Methoden als Fabriken behandeln und eine neue Sammlung mit dem hinzugefügten Element zurückgeben - was mit der aktuellen Signatur nicht kompatibel ist. Dies könnte jedoch durch die Verwendung eines anderen Methodennamens wie ImmutableList.append () oder .addAt () und ImmutableMap.putEntry () umgangen werden. Die resultierende Ausführlichkeit würde durch die Vorteile der Arbeit mit unveränderlichen Auflistungen mehr als aufgewogen, und das Typsystem würde Fehler beim Aufrufen der falschen Methode verhindern. Im Laufe der Zeit könnten die alten Methoden veraltet sein.

Gewinne unveränderlicher Sammlungen:

  • Einfachheit - Das Denken in Bezug auf Code ist einfacher, wenn sich die zugrunde liegenden Daten nicht ändern.
  • Dokumentation - Wenn eine Methode eine unveränderliche Sammlungsschnittstelle verwendet, wissen Sie, dass diese Sammlung nicht geändert wird. Wenn eine Methode eine unveränderliche Auflistung zurückgibt, wissen Sie, dass Sie sie nicht ändern können.
  • Parallelität - unveränderliche Sammlungen können sicher über mehrere Threads hinweg gemeinsam genutzt werden.

Als jemand, der Sprachen gekostet hat, die Unveränderlichkeit annehmen, ist es sehr schwer, in den Wilden Westen der grassierenden Mutation zurückzukehren. Die Sammlungen von Clojure (Sequenzabstraktion) bieten bereits alles, was Java 8-Sammlungen bieten, sowie Unveränderlichkeit (obwohl möglicherweise zusätzlicher Speicher und Zeit aufgrund synchronisierter verknüpfter Listen anstelle von Streams benötigt wird). Scala hat sowohl veränderbare als auch unveränderliche Sammlungen mit einer ganzen Reihe von Operationen, und obwohl diese Operationen eifrig sind, gibt der Aufruf von .iterator eine träge Sichtweise (und es gibt andere Möglichkeiten, sie träge zu bewerten). Ich sehe nicht, wie Java ohne unveränderliche Sammlungen weiter konkurrieren kann.

Kann mich jemand auf die Geschichte oder Diskussion darüber hinweisen? Sicherlich ist es irgendwo öffentlich.

GlenPeterson
quelle
9
In diesem Zusammenhang hat Ayende kürzlich über Sammlungen und unveränderliche Sammlungen in C # mit Benchmarks gebloggt. ayende.com/blog/tags/performance - tl; dr - Unveränderlichkeit ist langsam .
Oded
20
Mit deiner Hierarchie kann ich dir eine ImmutableList geben und sie dann an dir ändern, wenn du nicht erwartest, dass sie viele Dinge kaputt machen kann, da du nur constSammlungen hast
Ratschenfreak
18
@Oded Unveränderlichkeit ist langsam, Sperren jedoch auch. So ist die Aufrechterhaltung einer Geschichte. Einfachheit / Korrektheit ist in vielen Situationen Schnelligkeit wert. Bei kleinen Sammlungen spielt Geschwindigkeit keine Rolle. Die Analyse von Ayende basiert auf der Annahme, dass Sie keinen Verlauf, keine Sperrung oder Einfachheit benötigen und mit einem großen Datensatz arbeiten. Manchmal stimmt das, aber es ist keine Sache, die immer besser ist. Es gibt Kompromisse.
GlenPeterson
5
@ GlenPeterson das ist, was defensive Kopien und Collections.unmodifiable*()sind für. Aber behandeln Sie diese nicht als unveränderlich, wenn sie nicht sind
Ratschenfreak
13
Wie? Wenn Ihre Funktion ein ImmutableListin diesem Diagramm nimmt, können Leute in einem veränderlichen überschreiten List? Nein, das ist eine sehr schlimme Verletzung von LSP.
Telastyn

Antworten:

113

Weil unveränderliche Sammlungen unbedingt das Teilen erfordern, um verwendbar zu sein. Andernfalls legt jede einzelne Operation irgendwo eine andere Liste auf dem Heap ab. Völlig unveränderliche Sprachen wie Haskell erzeugen erstaunliche Mengen an Müll, ohne aggressive Optimierungen und gemeinsame Nutzung. Eine Sammlung, die nur mit <50 Elementen verwendbar ist, ist es nicht wert, in die Standardbibliothek aufgenommen zu werden.

Darüber hinaus haben unveränderliche Sammlungen oft grundlegend andere Implementierungen als ihre veränderlichen Gegenstücke. Stellen Sie sich zum Beispiel vor ArrayList, eine effiziente unveränderliche Datei ArrayListwäre überhaupt kein Array! Es sollte mit einem ausgeglichenen Baum mit einem großen Verzweigungsfaktor implementiert werden, Clojure verwendet 32 ​​IIRC. Es ist ebenso ein Leistungsfehler wie ein Speicherverlust, veränderliche Sammlungen durch Hinzufügen eines funktionalen Updates "unveränderlich" zu machen.

Darüber hinaus ist das Teilen in Java nicht möglich. Java bietet zu viele uneingeschränkte Möglichkeiten zur Änderung und Referenzgleichheit, um das Teilen "nur eine Optimierung" zu machen. Es würde Sie wahrscheinlich ein bisschen ärgern, wenn Sie ein Element in einer Liste ändern könnten und feststellen würden, dass Sie gerade ein Element in den anderen 20 Versionen dieser Liste geändert haben.

Dies schließt auch große Klassen von sehr wichtigen Optimierungen für eine effiziente Unveränderlichkeit, gemeinsame Nutzung, Stream-Fusion, wie Sie es nennen, aus. (Das wäre ein guter Slogan für FP-Evangelisten)

jozefg
quelle
21
In meinem Beispiel ging es um unveränderliche Schnittstellen . Java könnte eine vollständige Suite von sowohl veränderlichen als auch unveränderlichen Implementierungen dieser Schnittstellen bereitstellen, die die notwendigen Kompromisse eingehen würden. Es ist Sache des Programmierers, je nach Bedarf veränderlich oder unveränderlich zu wählen. Programmierer müssen wissen, wann sie eine Liste oder ein Set verwenden müssen. Im Allgemeinen benötigen Sie die veränderbare Version erst, wenn Sie ein Leistungsproblem haben. In diesem Fall ist sie möglicherweise nur als Builder erforderlich. In jedem Fall wäre die unveränderliche Schnittstelle ein Gewinn für sich.
GlenPeterson
4
Ich lese Ihre Antwort noch einmal und ich denke, Sie sagen, dass Java eine grundlegende Annahme der Veränderlichkeit hat (z. B. Java-Bohnen) und dass die Sammlungen nur die Spitze des Eisbergs sind und das Herausschneiden dieser Spitze das zugrunde liegende Problem nicht lösen wird. Ein gültiger Punkt. Ich könnte diese Antwort akzeptieren und meine Einführung von Scala beschleunigen! :-)
GlenPeterson
8
Ich bin nicht sicher, ob unveränderliche Sammlungen die Möglichkeit erfordern, gemeinsame Teile zu verwenden, um nützlich zu sein. Der häufigste unveränderliche Typ in Java, eine unveränderliche Sammlung von Zeichen, die zum Freigeben verwendet wird, dies jedoch nicht mehr. Das wichtigste, was es nützlich macht, ist die Fähigkeit, Daten schnell von a Stringin a zu kopieren StringBuffer, sie zu manipulieren und dann in eine neue unveränderliche zu kopieren String. Die Verwendung eines solchen Musters mit Mengen und Listen ist möglicherweise so gut wie die Verwendung unveränderlicher Typen, die die Produktion leicht veränderter Instanzen erleichtern sollen, könnte aber immer noch besser sein ...
supercat
3
Es ist durchaus möglich, eine unveränderliche Sammlung in Java mithilfe der Freigabe zu erstellen. Die in der Sammlung gespeicherten Elemente sind Referenzen und ihre Referenzen können mutiert sein - na und? Durch ein solches Verhalten werden bereits vorhandene Sammlungen wie HashMap und TreeSet zerstört, diese sind jedoch in Java implementiert. Wenn mehrere Auflistungen Verweise auf dasselbe Objekt enthalten, ist zu erwarten, dass das Ändern des Objekts zu einer sichtbaren Änderung führt, wenn es in allen Auflistungen angezeigt wird.
Solomonoffs Geheimnis
4
Jozefg, es ist durchaus möglich, effiziente unveränderliche Sammlungen in JVM mit struktureller Freigabe zu implementieren. Scala und Clojure haben sie als Teil ihrer Standardbibliothek, beide Implementierungen basieren auf Phil Bagwells HAMT (Hash Array Mapped Trie). Ihre Aussage zu Clojure bei der Implementierung unveränderlicher Datenstrukturen mit BALANCED-Bäumen ist völlig falsch.
sesm
77

Eine veränderbare Sammlung ist kein Untertyp einer unveränderlichen Sammlung. Stattdessen sind veränderbare und unveränderliche Sammlungen gleichrangige Nachkommen von lesbaren Sammlungen. Leider verschwimmen die Begriffe "lesbar", "schreibgeschützt" und "unveränderlich", obwohl sie drei verschiedene Bedeutungen haben.

  • Eine lesbare Auflistungsbasisklasse oder ein lesbarer Schnittstellentyp versprechen, dass Elemente gelesen werden dürfen, und bieten keine direkten Mittel zum Ändern der Auflistung, garantieren jedoch nicht, dass der Code, der die Referenz empfängt, diese nicht so umwandeln oder manipulieren kann, dass Änderungen möglich sind.

  • Eine schreibgeschützte Sammlungsschnittstelle enthält keine neuen Mitglieder, sondern sollte nur von einer Klasse implementiert werden, die verspricht, dass es keine Möglichkeit gibt, einen Verweis darauf so zu manipulieren, dass die Sammlung mutiert oder ein Verweis auf etwas empfangen wird das könnte so sein. Es wird jedoch nicht zugesichert, dass die Sammlung nicht durch etwas anderes modifiziert wird, das einen Verweis auf die Interna enthält. Beachten Sie, dass eine schreibgeschützte Erfassungsschnittstelle die Implementierung durch veränderbare Klassen möglicherweise nicht verhindern kann, aber angeben kann, dass jede Implementierung oder von einer Implementierung abgeleitete Klasse, die eine Mutation zulässt, als "unzulässige" Implementierung oder Ableitung einer Implementierung betrachtet werden soll .

  • Eine unveränderliche Sammlung ist eine Sammlung, die immer dieselben Daten enthält, solange ein Verweis darauf vorhanden ist. Jede Implementierung einer unveränderlichen Schnittstelle, die als Antwort auf eine bestimmte Anforderung nicht immer dieselben Daten zurückgibt, ist fehlerhaft.

Es ist manchmal nützlich , stark assoziierten wandelbar und unveränderlich Kollektionstypen zu haben , die beide oder aus dem gleichen „lesbar“ Typ ableiten implementieren und die lesbare Art zu haben sind AsImmutable, AsMutableund AsNewMutableMethoden. Ein solches Design kann es Code ermöglichen, der die Daten in einer Sammlung beibehalten möchte, aufzurufen AsImmutable. Diese Methode erstellt eine defensive Kopie, wenn die Sammlung veränderbar ist, überspringt die Kopie jedoch, wenn sie bereits unveränderbar ist.

Superkatze
quelle
1
Gute Antwort. Unveränderliche Sammlungen können Ihnen eine ziemlich starke Garantie in Bezug auf Thread-Sicherheit geben und wie Sie mit der Zeit über sie nachdenken können. Bei einer Readable / Read-only-Auflistung ist dies nicht der Fall. Um dem Prinzip der Liskov-Substitution gerecht zu werden, sollten schreibgeschützt und unveränderlich wahrscheinlich ein abstrakter Basistyp mit endgültiger Methode und privaten Mitgliedern sein, um sicherzustellen, dass keine abgeleitete Klasse die durch den Typ gegebene Garantie zerstören kann. Oder es sollte sich um einen ganz konkreten Typ handeln, der entweder eine Sammlung umschließt (schreibgeschützt) oder immer eine defensive Kopie erstellt (unveränderlich). So macht es guava ImmutableList.
Laurent Bourgault-Roy
1
@ LaurentBourgault-Roy: Es gibt Vorteile sowohl für versiegelte als auch für vererbbare unveränderliche Typen. Wenn man nicht zulassen will, dass eine uneheliche abgeleitete Klasse seine Invarianten zerstört, können versiegelte Typen dagegen Schutz bieten, während vererbbare Klassen keine bieten. Andererseits ist es möglich, dass Code, der etwas über die darin enthaltenen Daten weiß, diese viel kompakter speichert als ein Typ, der nichts darüber weiß. Betrachten Sie beispielsweise einen ReadableIndexedIntSequence-Typ, der eine Sequenz von int, mit Methoden getLength()und kapselt getItemAt(int).
Supercat
1
@ LaurentBourgault-Roy: Unter der Voraussetzung von a ReadableIndexedIntSequencekönnte man eine Instanz eines durch ein Array gesicherten unveränderlichen Typs erzeugen, indem man alle Elemente in ein Array kopiert. Nehmen wir jedoch an, dass eine bestimmte Implementierung einfach 16777216 für die Länge und ((long)index*index)>>24für jedes Element zurückgibt . Das wäre eine legitime unveränderliche Folge von ganzen Zahlen, aber das Kopieren in ein Array wäre eine enorme Verschwendung von Zeit und Speicher.
Supercat
1
Ich stimme vollkommen zu. Meine Lösung bietet Ihnen (bis zu einem gewissen Punkt) Korrektheit , aber um Leistung mit großen Datenmengen zu erzielen, müssen Sie von Anfang an über eine beständige Struktur und ein beständiges Design verfügen, um unveränderlich zu sein. Für eine kleine Sammlung können Sie jedoch von Zeit zu Zeit eine unveränderliche Kopie mitnehmen. Ich erinnere mich, dass Scala verschiedene Programme analysiert und festgestellt hat, dass etwa 90% der instanziierten Listen 10 oder weniger Elemente lang waren.
Laurent Bourgault-Roy
1
@ LaurentBourgault-Roy: Die grundsätzliche Frage ist, ob man den Leuten vertraut, um keine kaputten Implementierungen oder abgeleiteten Klassen zu produzieren. Wenn dies der Fall ist und die Interfaces / Basisklassen asMutable / asImmutable-Methoden bereitstellen, kann die Leistung möglicherweise um viele Größenordnungen verbessert werden [z. B. werden die Kosten für das Aufrufen asImmutableeiner Instanz der oben definierten Sequenz mit den Kosten für das Erstellen verglichen eine unveränderliche Array-gesicherte Kopie. Ich würde davon ausgehen, dass es wahrscheinlich besser ist, Schnittstellen für solche Zwecke zu definieren, als Ad-hoc-Ansätze zu verwenden. IMHO, der größte Grund ...
Supercat
15

Das Java Collections Framework bietet die Möglichkeit, mithilfe von sechs statischen Methoden in der Klasse java.util.Collections eine schreibgeschützte Version einer Sammlung zu erstellen :

Wie bereits in den Kommentaren zur ursprünglichen Frage erwähnt, gelten die zurückgegebenen Sammlungen möglicherweise nicht als unveränderlich, da die tatsächlichen Objekte, auf die die Sammlung verweist, auch dann nicht geändert werden können (es können keine Mitglieder zu einer solchen Sammlung hinzugefügt oder aus dieser entfernt werden) können geändert werden, wenn der Objekttyp dies zulässt.

Dieses Problem bleibt jedoch bestehen, unabhängig davon, ob der Code ein einzelnes Objekt oder eine nicht änderbare Auflistung von Objekten zurückgibt. Wenn der Typ zulässt, dass seine Objekte mutiert werden, dann wurde diese Entscheidung im Design des Typs getroffen, und ich sehe nicht, wie eine Änderung an der JCF dies ändern könnte. Wenn Unveränderlichkeit wichtig ist, sollten die Mitglieder einer Sammlung unveränderlichen Typs sein.

Arkanon
quelle
4
Das Design der nicht änderbaren Sammlungen wäre erheblich verbessert worden, wenn die Wrapper einen Hinweis darauf enthielten, ob das zu verpackende Objekt bereits unveränderlich war, und es immutableListusw. Factory-Methoden gab, die einen schreibgeschützten Wrapper um eine Kopie eines übergebenen Dokuments zurückgaben Liste, es sei denn, die übergebene Liste war bereits unveränderlich . Es wäre einfach, benutzerdefinierte Typen wie diesen zu erstellen, aber für ein Problem: Es gibt keine Möglichkeit für eine joesCollections.immutableListMethode, zu erkennen, dass das von zurückgegebene Objekt nicht kopiert werden muss fredsCollections.immutableList.
Supercat
8

Das ist eine sehr gute Frage. Ich mag die Idee, dass von all dem Code, der in Java geschrieben ist und auf Millionen von Computern auf der ganzen Welt läuft, jeden Tag rund um die Uhr ungefähr die Hälfte der gesamten Taktzyklen verschwendet werden muss, nur um Sicherheitskopien von Sammlungen anzufertigen, die es gibt von Funktionen zurückgegeben werden. (Und Müllsammeln dieser Sammlungen Millisekunden nach ihrer Erstellung.)

Ein Prozentsatz der Java-Programmierer ist sich der Existenz der unmodifiableCollection()Methodenfamilie der CollectionsKlasse bewusst , aber selbst unter ihnen kümmern sich viele einfach nicht darum.

Und ich kann ihnen keine Vorwürfe machen: Eine Schnittstelle, die vorgibt, Lese- und Schreibzugriff zu haben, aber einen UnsupportedOperationExceptionFehler auslöst, wenn Sie den Fehler machen, eine der Schreibmethoden aufzurufen, ist ziemlich böse!

Nun, eine Schnittstelle, wie Collectiondie die und Methoden fehlen würde add(), wäre keine "ImmutableCollection" -Schnittstelle; Es wäre eine "UnmodifiableCollection" -Schnittstelle. Tatsächlich könnte es niemals eine "ImmutableCollection" -Schnittstelle geben, da Unveränderlichkeit eine Natur einer Implementierung ist und kein Merkmal einer Schnittstelle. Ich weiß, das ist nicht sehr klar. lassen Sie mich erklären.remove()clear()

Angenommen, jemand übergibt Ihnen eine solche schreibgeschützte Erfassungsschnittstelle. Ist es sicher, es an einen anderen Thread weiterzuleiten? Wenn Sie sicher wären, dass es sich um eine wirklich unveränderliche Sammlung handelt, wäre die Antwort "Ja". Da es sich leider um eine Schnittstelle handelt, wissen Sie nicht, wie sie implementiert ist, daher muss die Antwort ein Nein sein : Nach allem, was Sie wissen, kann es sich um eine (für Sie) unveränderbare Ansicht einer Sammlung handeln, die tatsächlich veränderlich ist. Wenn Sie Collections.unmodifiableCollection()also versuchen, von einem anderen Thread zu lesen, während dieser geändert wird, führt dies dazu, dass beschädigte Daten gelesen werden.

Also, was Sie im Wesentlichen beschrieben haben, ist eine Reihe von nicht "unveränderlichen", sondern "nicht modifizierbaren" Sammlungsschnittstellen. Es ist wichtig zu verstehen, dass "Nicht modifizierbar" bedeutet, dass jeder, der auf eine solche Schnittstelle verweist, daran gehindert wird, die zugrunde liegende Auflistung zu modifizieren, und dass dies einfach deshalb verhindert wird, weil die Schnittstelle keine Modifizierungsmethoden aufweist, und nicht, weil die zugrunde liegende Auflistung notwendigerweise unveränderlich ist. Die zugrunde liegende Sammlung ist möglicherweise sehr gut veränderlich. Sie haben keine Kenntnis davon und keine Kontrolle darüber.

Um unveränderliche Sammlungen zu haben, müssten sie Klassen sein , keine Schnittstellen!

Diese unveränderlichen Auflistungsklassen müssten endgültig sein, sodass Sie sicher sind, dass sich eine solche Auflistung als unveränderliche Auflistung verhält, unabhängig davon, was Sie oder andere Personen, die einen Verweis darauf haben, möglicherweise mach damit.

Um also einen vollständigen Satz von Sammlungen in Java (oder einer anderen deklarativen imperativen Sprache) zu haben, benötigen wir Folgendes:

  1. Ein Satz von unmodifiable Sammlung Schnittstellen .

  2. Eine Reihe veränderlicher Erfassungsschnittstellen , die die nicht veränderbaren Schnittstellen erweitern.

  3. Eine Reihe von veränderbaren Auflistungsklassen , die die veränderbaren Schnittstellen und im weiteren Sinne auch die nicht veränderbaren Schnittstellen implementieren.

  4. Eine Reihe von unveränderlicher Sammlung Klassen , die als nicht änderbar Schnittstellen Implementierung, sondern vor allem als Klassen herumgereicht, um Unveränderlichkeit zu gewährleisten.

Ich habe all das zum Spaß implementiert und ich benutze sie in Projekten, und sie wirken wie ein Zauber.

Der Grund, warum sie nicht Teil der Java-Laufzeit sind, ist wahrscheinlich, dass angenommen wurde, dass dies zu viel / zu komplex / zu schwer zu verstehen ist.

Persönlich denke ich, dass das, was ich oben beschrieben habe, nicht einmal genug ist; eine weitere Sache, die als notwendig erscheint, ist eine Reihe von veränderlichen Schnittstellen und Klassen für strukturelle Unveränderlichkeit . (Das kann einfach als "starr" bezeichnet werden, weil das Präfix "strukturell festlegbar" zu lang ist.)

Mike Nakis
quelle
Gute Argumente. Zwei Details: 1. Unveränderliche Sammlungen erfordern bestimmte Methodensignaturen, insbesondere (am Beispiel einer Liste): List<T> add(T t)- Alle "mutator" -Methoden müssen eine neue Sammlung zurückgeben, die die Änderung widerspiegelt. 2. Ob gut oder schlecht, Interfaces repräsentieren oft einen Vertrag zusätzlich zu einer Unterschrift. Serializable ist eine solche Schnittstelle. In ähnlicher Weise setzt Comparable voraus, dass Sie Ihre compareTo()Methode korrekt implementieren , um korrekt zu funktionieren und im Idealfall mit equals()und kompatibel zu sein hashCode().
GlenPeterson,
Oh, ich hatte nicht einmal die Unveränderlichkeit von Mutationen nach Kopien im Sinn. Was ich oben geschrieben habe, bezieht sich auf einfache unveränderliche Sammlungen, die wirklich keine Methoden wie haben add(). Aber ich nehme an, wenn Mutatormethoden zu den unveränderlichen Klassen hinzugefügt würden, müssten sie auch unveränderliche Klassen zurückgeben. Wenn dort ein Problem lauert, sehe ich es nicht.
Mike Nakis
Ist Ihre Implementierung öffentlich verfügbar? Ich hätte das vor Monaten fragen sollen. Wie auch immer, meins ist: github.com/GlenKPeterson/UncleJim
GlenPeterson
4
Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?Angenommen, jemand übergibt Ihnen eine Instanz einer veränderlichen Erfassungsschnittstelle. Ist es sicher, eine Methode dafür aufzurufen? Sie wissen nicht, dass die Implementierung keine ewige Schleife durchläuft, keine Ausnahme auslöst oder den Vertrag der Schnittstelle vollständig ignoriert. Warum eine Doppelmoral speziell für unveränderliche Sammlungen?
Doval
1
IMHO ist Ihre Argumentation gegen veränderbare Schnittstellen falsch. Sie können eine veränderbare Implementierung unveränderlicher Schnittstellen schreiben, und dann bricht es. Sicher. Aber das ist deine Schuld, weil du den Vertrag verletzt. Hör einfach auf damit. Es ist nicht anders, als ein zu brechen, SortedSetindem die Menge mit einer nicht konformen Implementierung unterklassifiziert wird. Oder indem Sie eine inkonsistente übergeben Comparable. Fast alles kann kaputt gehen, wenn Sie wollen. Ich denke, das hat @Doval mit "Doppelmoral" gemeint.
Maaartinus
2

Unveränderliche Auflistungen können im Vergleich zueinander zutiefst rekursiv und nicht unangemessen ineffizient sein, wenn die Objektgleichheit durch secureHash erfolgt. Dies nennt man einen merkle Wald. Es kann pro Sammlung oder in Teilen davon wie ein (selbstausgleichender binärer) AVL-Baum für eine sortierte Karte sein.

Sofern nicht alle Java-Objekte in diesen Auflistungen eine eindeutige ID oder einen zu hashenden Bitstring haben, muss die Auflistung nichts hashen, um sich selbst einen eindeutigen Namen zu geben.

Beispiel: Auf meinem 4 x 1,6-GHz-Laptop kann ich 200K sha256s pro Sekunde mit der kleinsten Größe ausführen, die in einen Hash-Zyklus (bis zu 55 Byte) passt, im Vergleich zu 500K HashMap-Operationen oder 3M-Operationen in einer Hash-Tabelle von Longs. 200 K / log (collectionSize) neue Sammlungen pro Sekunde sind für einige Dinge schnell genug, bei denen Datenintegrität und anonyme globale Skalierbarkeit wichtig sind.

Ben Rayfield
quelle
-3

Performance. Sammlungen können von Natur aus sehr groß sein. Das Kopieren von 1000 Elementen in eine neue Struktur mit 1001 Elementen anstelle des Einfügens eines einzelnen Elements ist einfach schrecklich.

Parallelität. Wenn mehrere Threads ausgeführt werden, möchten sie möglicherweise die aktuelle Version der Sammlung abrufen und nicht die Version, die vor 12 Stunden beim Starten des Threads übergeben wurde.

Lager. Mit unveränderlichen Objekten in einer Umgebung mit mehreren Threads können Sie am Ende Dutzende Kopien desselben Objekts an verschiedenen Punkten seines Lebenszyklus erhalten. Egal für ein Kalender- oder Datumsobjekt, aber wenn es sich um eine Sammlung von 10.000 Widgets handelt, werden Sie getötet.

James Anderson
quelle
12
Unveränderliche Sammlungen müssen nur kopiert werden, wenn Sie sie nicht freigeben können, da sie wie Java durchgängig veränderlich sind. Parallelität ist bei unveränderlichen Sammlungen im Allgemeinen einfacher, da für sie keine Sperrung erforderlich ist. und für die Sichtbarkeit können Sie immer einen veränderlichen Verweis auf eine unveränderliche Sammlung haben (in OCaml üblich). Mit der Freigabe können Updates im Wesentlichen kostenlos sein. Möglicherweise führen Sie logarithmisch mehr Zuweisungen durch als bei einer veränderlichen Struktur. Bei einer Aktualisierung können jedoch viele abgelaufene Unterobjekte sofort freigegeben oder wiederverwendet werden, sodass Sie nicht unbedingt einen höheren Speicheraufwand haben.
Jon Purdy
4
Paar Probleme. Die Sammlungen in Clojure und Scala sind beide unveränderlich, unterstützen jedoch leichte Kopien. Das Hinzufügen von Element 1001 bedeutet, dass weniger als 33 Elemente kopiert und ein paar neue Zeiger erstellt werden. Wenn Sie eine veränderbare Sammlung über mehrere Threads hinweg freigeben, treten beim Ändern aller Arten von Synchronisierungsproblemen auf. Operationen wie "remove ()" sind albtraumhaft. Unveränderliche Sammlungen können auch veränderlich erstellt und dann einmal in eine unveränderliche Version kopiert werden, die sicher für die gemeinsame Nutzung in mehreren Threads ist.
GlenPeterson
4
Parallelität als Argument gegen Unveränderlichkeit zu verwenden, ist ungewöhnlich. Auch Duplikate.
Tom Hawtin - Tackline
4
Ein bisschen verärgert über die Abstimmungen hier unten. Das OP fragte, warum es keine unveränderlichen Sammlungen implementiere, und ich gab eine überlegte Antwort auf die Frage. Vermutlich ist die einzig akzeptable Antwort unter den Modebewussten "weil sie einen Fehler gemacht haben". Ich habe tatsächlich einige Erfahrung damit, große Codestücke mithilfe der ansonsten ausgezeichneten BigDecimal-Klasse zu refaktorieren, und zwar allein aufgrund der schlechten Leistung, da die Unveränderlichkeit 512-mal höher ist als bei der Verwendung eines Double plus ein bisschen Herumspielen, um die Dezimalstellen zu korrigieren.
James Anderson
3
@JamesAnderson: Meine Probleme mit Ihrer Antwort: "Leistung" - Sie können sagen, dass echte unveränderliche Sammlungen immer eine Form der Freigabe und Wiederverwendung implementieren, um genau das Problem zu vermeiden, das Sie beschreiben. "Nebenläufigkeit" - das Argument lautet "Wenn Sie eine Veränderlichkeit wünschen, funktioniert ein unveränderliches Objekt nicht." Ich meine, wenn es den Begriff "neueste Version des Gleichen" gibt, muss etwas mutieren, entweder das Ding selbst oder etwas, das das Ding besitzt. Und in "Storage" scheinen Sie zu sagen, dass Mutabilität manchmal nicht erwünscht ist.
JHOMINAL