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.
quelle
const
Sammlungen hastCollections.unmodifiable*()
sind für. Aber behandeln Sie diese nicht als unveränderlich, wenn sie nicht sindImmutableList
in diesem Diagramm nimmt, können Leute in einem veränderlichen überschreitenList
? Nein, das ist eine sehr schlimme Verletzung von LSP.Antworten:
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 DateiArrayList
wä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)
quelle
String
in a zu kopierenStringBuffer
, sie zu manipulieren und dann in eine neue unveränderliche zu kopierenString
. 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 ...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
,AsMutable
undAsNewMutable
Methoden. Ein solches Design kann es Code ermöglichen, der die Daten in einer Sammlung beibehalten möchte, aufzurufenAsImmutable
. Diese Methode erstellt eine defensive Kopie, wenn die Sammlung veränderbar ist, überspringt die Kopie jedoch, wenn sie bereits unveränderbar ist.quelle
int
, mit MethodengetLength()
und kapseltgetItemAt(int)
.ReadableIndexedIntSequence
kö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)>>24
fü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.asImmutable
einer 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 ...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.
quelle
immutableList
usw. 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 einejoesCollections.immutableList
Methode, zu erkennen, dass das von zurückgegebene Objekt nicht kopiert werden mussfredsCollections.immutableList
.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 derCollections
Klasse 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
UnsupportedOperationException
Fehler auslöst, wenn Sie den Fehler machen, eine der Schreibmethoden aufzurufen, ist ziemlich böse!Nun, eine Schnittstelle, wie
Collection
die die und Methoden fehlen würdeadd()
, 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:
Ein Satz von unmodifiable Sammlung Schnittstellen .
Eine Reihe veränderlicher Erfassungsschnittstellen , die die nicht veränderbaren Schnittstellen erweitern.
Eine Reihe von veränderbaren Auflistungsklassen , die die veränderbaren Schnittstellen und im weiteren Sinne auch die nicht veränderbaren Schnittstellen implementieren.
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.)
quelle
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 IhrecompareTo()
Methode korrekt implementieren , um korrekt zu funktionieren und im Idealfall mitequals()
und kompatibel zu seinhashCode()
.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.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?SortedSet
indem die Menge mit einer nicht konformen Implementierung unterklassifiziert wird. Oder indem Sie eine inkonsistente übergebenComparable
. Fast alles kann kaputt gehen, wenn Sie wollen. Ich denke, das hat @Doval mit "Doppelmoral" gemeint.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.
quelle
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.
quelle