it-swarm.com.de

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

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 bewirken a Hervorragende Arbeit bei der Verkettung von Transformationen in sehr schnell gestreamte Vorgänge. Wenn man bedenkt, wie gut sie erstklassige Funktionen und Funktionsmethoden für Sammlungen hinzugefügt haben, warum haben sie es nicht geschafft, unveränderliche Sammlungen oder sogar unveränderliche Sammlungsschnittstellen bereitzustellen?

Ohne einen vorhandenen Code zu ändern, könnte das Java -Team jederzeit unveränderliche Schnittstellen hinzufügen, die mit den veränderlichen identisch sind, abzüglich der "set" -Methoden, und die vorhandenen Schnittstellen von diesen aus erweitern, wie z diese:

                  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 zurückgeben, die das hinzugefügte Element enthält - was mit der aktuellen Signatur nicht kompatibel ist. Dies könnte jedoch umgangen werden, indem ein anderer Methodenname wie ImmutableList.append () oder .addAt () und ImmutableMap.putEntry () verwendet wird. Die resultierende Ausführlichkeit würde durch die Vorteile der Arbeit mit unveränderlichen Sammlungen 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.

Siege unveränderlicher Sammlungen:

  • Einfachheit - Das Denken über 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 Sammlung zurückgibt, wissen Sie, dass Sie sie nicht ändern können.
  • Parallelität - unveränderliche Sammlungen können sicher über Threads hinweg gemeinsam genutzt werden.

Als jemand, der Sprachen gekostet hat, die Unveränderlichkeit voraussetzen, ist es sehr schwierig, in den Wilden Westen der grassierenden Mutation zurückzukehren. Clojures Sammlungen (Sequenzabstraktion) enthalten bereits alles, was Java 8 Sammlungen bieten, plus Unveränderlichkeit (obwohl möglicherweise zusätzlicher Speicher und zusätzliche Zeit aufgrund synchronisierter verknüpfter Listen anstelle von Streams benötigt werden). Scala hat sowohl veränderbare als auch unveränderliche Sammlungen mit einem vollständigen Satz von Operationen, und obwohl diese Operationen eifrig sind, gibt das Aufrufen von .iterator eine träge Ansicht (und es gibt andere Möglichkeiten, sie träge zu bewerten). Ich verstehe nicht, wie Java kann ohne unveränderliche Sammlungen weiter konkurrieren.

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

136
GlenPeterson

Weil unveränderliche Sammlungen unbedingt eine gemeinsame Nutzung erfordern, um verwendet werden zu können. Andernfalls legt jede einzelne Operation irgendwo eine ganz andere Liste auf dem Heap ab. Sprachen, die völlig unveränderlich sind, wie Haskell, erzeugen erstaunliche Mengen an Müll ohne aggressive Optimierungen und gemeinsame Nutzung. Eine Sammlung, die nur mit <50 Elementen verwendet werden kann, lohnt sich nicht in die Standardbibliothek aufzunehmen.

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

Darüber hinaus ist das Teilen in Java nicht möglich. Java bietet zu viele uneingeschränkte Hooks für Veränderlichkeit und Referenzgleichheit, um das Teilen "nur eine Optimierung" zu machen. Es würde Sie wahrscheinlich ein wenig ärgern, wenn Sie ein Element in einer Liste ändern und Sie erkennen könnten habe gerade ein Element in den anderen 20 Versionen dieser Liste geändert, die du hattest.

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

115
Daniel Gratzer

Eine veränderbare Sammlung ist kein Subtyp einer unveränderlichen Sammlung. Stattdessen sind veränderbare und unveränderliche Sammlungen Geschwisternachkommen lesbarer Sammlungen. Leider scheinen die Konzepte "lesbar", "schreibgeschützt" und "unveränderlich" verschwommen zu sein, obwohl sie drei verschiedene Dinge bedeuten.

  • Eine lesbare Sammlungsbasisklasse oder ein lesbarer Schnittstellentyp verspricht, dass Elemente gelesen werden können, und bietet keine direkte Möglichkeit zum Ändern der Sammlung, garantiert jedoch nicht, dass Code, der die Referenz empfängt, sie nicht so umwandeln oder manipulieren kann, dass eine Änderung möglich ist.

  • 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 wird oder ein Verweis auf etwas erhalten wird das könnte so sein. Es verspricht jedoch nicht, dass die Sammlung nicht durch etwas anderes geändert wird, das einen Verweis auf die Interna enthält. Beachten Sie, dass eine schreibgeschützte Erfassungsschnittstelle möglicherweise die Implementierung durch veränderbare Klassen nicht verhindern kann, aber angeben kann, dass jede Implementierung oder Klasse, die von einer Implementierung abgeleitet ist und eine Mutation ermöglicht, als "illegitime" Implementierung oder Ableitung einer Implementierung betrachtet wird .

  • 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 assoziierte veränderbare und unveränderliche Auflistungstypen zu haben, die denselben "lesbaren" Typ implementieren oder von diesem ableiten, und den lesbaren Typ AsImmutable, AsMutable und AsNewMutable Methoden. Durch ein solches Design kann Code, der die Daten in einer Sammlung beibehalten möchte, AsImmutable aufrufen. Diese Methode erstellt eine defensive Kopie, wenn die Sammlung veränderbar ist. Überspringen Sie die Kopie jedoch, wenn sie bereits unveränderlich ist.

79
supercat

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

Wie jemand in den Kommentaren zur ursprünglichen Frage ausgeführt hat, können die zurückgegebenen Sammlungen möglicherweise nicht als unveränderlich angesehen werden, da die tatsächlichen Objekte, auf die von der Sammlung verwiesen wird, obwohl die Sammlungen nicht geändert werden können (keine Mitglieder können zu einer solchen Sammlung hinzugefügt oder daraus entfernt werden) kann geändert werden, wenn ihr Objekttyp dies zulässt.

Dieses Problem bleibt jedoch bestehen, unabhängig davon, ob Code ein einzelnes Objekt oder eine nicht veränderbare Sammlung von Objekten zurückgibt. Wenn der Typ zulässt, dass seine Objekte mutiert werden, 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änderlich sein.

15
Arkanon

Das ist eine sehr gute Frage. Ich genieße es, die Idee zu unterhalten, 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 etwa die Hälfte der gesamten Taktzyklen verschwendet werden muss nichts anderes als das Erstellen von Sicherheitskopien von Sammlungen, die von Funktionen zurückgegeben werden. (Und das Sammeln dieser Sammlungen durch Müll diese Millisekunden nach ihrer Erstellung.)

Ein Prozentsatz von Java Programmierern ist sich der Existenz der unmodifiableCollection() Methodenfamilie der Collections Klasse bewusst, aber selbst unter ihnen tun viele einfach nichts. kümmere dich nicht darum.

Und ich kann ihnen keine Vorwürfe machen: Eine Schnittstelle, die vorgibt, gelesen und geschrieben zu werden, aber ein UnsupportedOperationException auslöst, wenn Sie den Fehler machen, eine ihrer 'Schreib'-Methoden aufzurufen, ist eine ziemlich böse Sache!

Eine Schnittstelle wie Collection, bei der die Methoden add(), remove() und clear() fehlen würden, wäre keine "ImmutableCollection" -Schnittstelle. Es wäre eine "UnmodizableCollection" -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.

Angenommen, jemand gibt Ihnen eine solche schreibgeschützte Erfassungsoberfläche. Ist es sicher, es an einen anderen Thread weiterzugeben? Wenn Sie sicher wüssten, dass es sich um eine wirklich unveränderliche Sammlung handelt, lautet die Antwort "Ja". Da es sich um eine Schnittstelle handelt, wissen Sie leider nicht, wie sie implementiert ist. Daher muss die Antwort no sein : Nach allem, was Sie wissen, kann es sich um eine (für Sie) nicht veränderbare Ansicht einer Sammlung handeln, die tatsächlich veränderlich ist (wie das, was Sie mit Collections.unmodifiableCollection() erhalten). Versuchen Sie also, daraus zu lesen, während sich ein anderer Thread befindet Eine Änderung würde zum Lesen beschädigter Daten führen.

Was Sie also im Wesentlichen beschrieben haben, ist eine Reihe von nicht "unveränderlichen", sondern "nicht modifizierbaren" Erfassungsschnittstellen. Es ist wichtig zu verstehen, dass "nicht modifizierbar" einfach bedeutet, dass jeder, der auf eine solche Schnittstelle verweist, daran gehindert wird, die zugrunde liegende Sammlung zu ändern, und dass dies einfach deshalb verhindert wird, weil der Schnittstelle keine Änderungsmethoden fehlen, und nicht, weil die zugrunde liegende Sammlung notwendigerweise unveränderlich ist. Die zugrunde liegende Sammlung könnte sehr wohl veränderlich sein; 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 Sammlungsklassen müssten endgültig sein, damit Sie, wenn Sie einen Verweis auf eine solche Sammlung erhalten, sicher wissen, dass sie sich wie eine unveränderliche Sammlung verhält, unabhängig davon, was Sie oder jemand anderes, der einen Verweis darauf hat, könnte 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. Eine Menge von nicht modifizierbar Sammlung Schnittstellen.

  2. Eine Menge von veränderlich Sammlung Schnittstellen, die die nicht veränderbaren erweitert.

  3. Eine Menge von veränderlich Sammlung Klassen Implementierung der veränderlichen Schnittstellen und im weiteren Sinne auch der nicht veränderbaren Schnittstellen.

  4. Eine Reihe von nveränderlich Sammlung Klassen, die die nicht veränderbaren Schnittstellen implementiert, aber meistens als Klassen weitergegeben wird, um Unveränderlichkeit zu gewährleisten.

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

Der Grund, warum sie nicht Teil der Laufzeit Java Laufzeit) sind, liegt wahrscheinlich darin, dass angenommen wurde, dass dies zu viel/zu komplex/zu schwer zu verstehen wäre.

Persönlich denke ich, dass das, was ich oben beschrieben habe, nicht einmal genug ist; Eine weitere Sache, die benötigt zu werden scheint, ist eine Reihe veränderbarer Schnittstellen und Klassen für strukturelle Unveränderlichkeit . (Was einfach "starr" genannt werden kann, weil das Präfix "Strukturell unveränderlich" zu lang ist.)

8
Mike Nakis

Unveränderliche Sammlungen können im Vergleich zueinander sehr rekursiv sein und sind nicht unangemessen ineffizient, 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 Sammlungen eine eindeutige ID oder eine Bitfolge zum Hashing haben, hat die Sammlung nichts zu haschen, um sich eindeutig zu benennen.

Beispiel: Auf meinem 4x1,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-Ops oder 3M-Ops in einer Hashtabelle von Longs. 200K/log (collectionSize) neue Sammlungen pro Sekunde sind schnell genug für einige Dinge, bei denen Datenintegrität und anonyme globale Skalierbarkeit wichtig sind.

2
Ben Rayfield