Umhüllen Sie ImmutableList in Java

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) { //   ArrayList,      -  arr = original.toArray(); } this.data = arr; } 

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.)

Source: https://habr.com/ru/post/de470257/


All Articles