Ich las den Artikel „Es wird keine unveränderlichen Sammlungen in Java geben - weder jetzt noch jemals“ und dachte, dass das Problem des Fehlens unveränderlicher Listen in Java, das den Autor traurig macht, in begrenztem Umfang ziemlich lösbar ist. Ich biete meine Gedanken und Code zu diesem Thema an.
(Dies ist ein Antwortartikel. Lesen Sie zuerst den Originalartikel.)
UnmodizableList vs ImmutableList
Die erste Frage, die sich stellt, lautet: Warum brauche ich eine UnmodifiableList
, wenn es eine ImmutableList
? Als Ergebnis der Diskussion werden in den Kommentaren des Originalartikels zwei Ideen zur Bedeutung von UnmodifiableList
gesehen:
- Die Methode empfängt eine
UnmodifiableList
, kann sie nicht selbst ändern, weiß jedoch, dass der Inhalt von einem anderen Thread geändert werden kann (und weiß, wie sie korrekt behandelt wird). - Andere Threads wirken sich nicht aus.
UnmodifiableList
und ImmutableList
sind für eine Methode gleichwertig, UnmodifiableList
jedoch als "leichtgewichtiger" verwendet.
Die erste Option scheint in der Praxis zu selten zu sein. Wenn Sie also eine „einfache“ Implementierung von ImmutableList
, ist UnmodifiableList
nicht unbedingt erforderlich. Daher werden wir es in Zukunft vergessen und nur ImmutableList
implementieren.
Erklärung des Problems
Wir werden die ImmutableList
Option implementieren:
- Die API sollte mit der regulären
List
API im Abschnitt "Lesen" identisch sein. Der Teil „Schreiben“ sollte fehlen. ImmutableList
und List
sollten nicht durch Vererbungsbeziehungen verbunden sein. Warum so - versteht den Originalartikel.- Es ist sinnvoll, die Implementierung analog zu
ArrayList
durchzuführen. Dies ist die einfachste Option. - Die Implementierung sollte nach Möglichkeit das Kopieren von Arrays vermeiden.
ImmutableList Implementierung
Zunächst beschäftigen wir uns mit der API. Wir untersuchen die Schnittstellen " Collection
und " List
" und kopieren den "lesenden" Teil von ihnen in unsere neuen Schnittstellen.
public interface ReadOnlyCollection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Object[] toArray(); <T> T[] toArray(T[] a); boolean containsAll(Collection<?> c); } public interface ReadOnlyList<E> extends ReadOnlyCollection<E> { E get(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); ReadOnlyList<E> subList(int fromIndex, int toIndex); }
Erstellen Sie als Nächstes die ImmutableList
Klasse. Die Signatur ähnelt ArrayList
(implementiert jedoch die ReadOnlyList
Schnittstelle anstelle von List
).
public class ImmutableList<E> implements ReadOnlyList<E>, RandomAccess, Cloneable, Serializable
Wir kopieren die Implementierung der Klasse aus ArrayList
und ArrayList
fest, werfen alles aus, was mit dem "Schreiben" zu tun hat, prüfen auf gleichzeitige Änderungen usw.
Designer werden wie folgt sein:
public ImmutableList() public ImmutableList(E[] original) public ImmutableList(Collection<? extends E> original)
Der erste erstellt eine leere Liste. Der zweite erstellt eine Liste durch Kopieren des Arrays. Wir können nicht auf das Kopieren verzichten, wenn wir unveränderliche Ergebnisse erzielen möchten. Der dritte ist interessanter. Ein ähnlicher ArrayList
Konstruktor kopiert auch Daten aus der Sammlung. Wir werden dasselbe tun, es sei denn, orginal
ist eine Instanz von ArrayList
oder Arrays$ArrayList
(dies wird von der Arrays.asList()
-Methode zurückgegeben). Wir können davon ausgehen, dass diese Fälle 90% der Konstruktoraufrufe abdecken.
In diesen Fällen "stehlen" wir das original
Array durch Reflexionen (es besteht die Hoffnung, dass dies schneller ist als das Kopieren von Gigabyte-Arrays). Die Essenz des "Diebstahls":
- Wir gelangen zum privaten Feld
original
, in dem das Array ArrayList.elementData
( ArrayList.elementData
). - Kopieren Sie den Link zum Array zu uns selbst
- in das Quellfeld null setzen
protected static final Field data_ArrayList; static { try { data_ArrayList = ArrayList.class.getDeclaredField("elementData"); data_ArrayList.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } } public ImmutableList(Collection<? extends E> original) { Object[] arr = null; if (original instanceof ArrayList) { try { arr = (Object[]) data_ArrayList.get(original); data_ArrayList.set(original, null); } catch (@SuppressWarnings("unused") IllegalArgumentException | IllegalAccessException e) { arr = null; } } if (arr == null) {
Als Vertrag gehen wir davon aus, dass beim Aufruf des Konstruktors die veränderbare Liste in eine ImmutableList
. Die ursprüngliche Liste kann danach nicht mehr verwendet werden. Bei der Verwendung wird eine NullPointerException
. Dies stellt sicher, dass sich das "gestohlene" Array nicht ändert und unsere Liste wirklich unveränderlich ist (außer in dem Fall, dass jemand durch Reflexionen zum Array gelangt).
Andere Klassen
Angenommen, wir entscheiden uns, eine ImmutableList
in einem realen Projekt zu verwenden.
Das Projekt interagiert mit Bibliotheken: empfängt von ihnen und sendet ihnen verschiedene Listen. In den allermeisten Fällen handelt es sich bei diesen Listen um eine ArrayList
. Mit der beschriebenen Implementierung von ImmutableList
können ImmutableList
die resultierende ArrayList
schnell in eine ImmutableList
konvertieren. Es ist auch erforderlich, die Konvertierung für an Bibliotheken gesendete Listen zu implementieren: ImmutableList
to List
. Für eine schnelle Konvertierung benötigen Sie einen ImmutableList
Wrapper, der List
implementiert und beim Versuch, in die Liste zu schreiben, Ausnahmen ImmutableList
(ähnlich wie Collections.unmodifiableList
).
Auch das Projekt selbst verarbeitet die Listen irgendwie. Es ist sinnvoll, eine MutableList
Klasse zu erstellen, die eine veränderbare Liste mit einer auf ArrayList
basierenden Implementierung darstellt. In diesem Fall können Sie das Projekt umgestalten, indem Sie für alle ArrayList
Klasse MutableList
die Absicht explizit deklariert: entweder ImmutableList
oder MutableList
.
MutableList
Sie eine schnelle Konvertierung von ImmutableList
zu MutableList
und umgekehrt. Gleichzeitig können wir im Gegensatz zur Konvertierung von ArrayList
in ImmutableList
die ursprüngliche Liste nicht mehr verderben.
Die Konvertierung erfolgt normalerweise langsam, wobei das Array kopiert wird. In Fällen, in denen sich die empfangene MutableList
nicht immer ändert, können Sie einen Wrapper MutableList
: MutableList
, der einen Verweis auf ImmutableList
speichert und zum "Lesen" von Methoden verwendet. Wenn eine "Schreib" -Methode aufgerufen wird, wird ImmutableList
nach dem Kopieren des Inhalts ImmutableList
Array für sich selbst, und dann funktioniert es bereits mit seinem Array (etwas entfernt Ähnliches ist in CopyOnWriteArrayList
).
Das Konvertieren von "Zurück" bedeutet, dass zum Zeitpunkt des MutableList
der Methode ein Snapshot des Inhalts der MutableList
empfangen wird. Auch hier können Sie in den meisten Fällen nicht auf das Kopieren eines Arrays verzichten, aber Sie können einen Wrapper MutableList
, um Fälle mehrerer Konvertierungen zu optimieren, zwischen denen sich der Inhalt der MutableList
nicht geändert hat. Eine weitere Option zum Konvertieren von "Zurück": Einige Daten werden in der MutableList
, und wenn die Datenerfassung abgeschlossen ist, muss die MutableList
für immer in eine ImmutableList
konvertiert werden. Es ist auch ohne Probleme mit einem anderen Wrapper implementiert.
Insgesamt
Die Ergebnisse des Experiments in Form von Code werden hier veröffentlicht
ImmutableList
selbst ist ImmutableList
, was im Abschnitt "Andere Klassen" beschrieben wird (noch nicht?).
Wir können davon ausgehen, dass die Prämisse des Originalartikels „Unveränderliche Sammlungen in Java werden nicht sein“ falsch ist.
Wenn es einen Wunsch gibt, ist es durchaus möglich, einen ähnlichen Ansatz zu verwenden. Ja, mit kleinen Krücken. Ja, nicht innerhalb des gesamten Systems, sondern nur in ihren Projekten (obwohl wenn viele eindringen, wird es allmählich in Bibliotheken gezogen).
Eine Sache: Wenn es einen Wunsch gibt ... (Tahiti, Tahiti ... Wir waren in keinem Tahiti! Sie ernähren uns hier gut.)