Nicht unterstützteOperationException in Java Collections Framework-Schnittstellen

12

Beim Durchsuchen des Java Collections Framework ist mir aufgefallen, dass einige der Schnittstellen den Kommentar enthalten (optional operation). Diese Methoden ermöglichen das Implementieren von Klassen über eine, UnsupportedOperationExceptionwenn sie diese Methode einfach nicht implementieren möchten.

Ein Beispiel hierfür ist die addAllMethode in der Set Interface.

Nun, wie in dieser Reihe von Fragen dargelegt, sind Schnittstellen ein bestimmender Vertrag für das, was die Nutzung erwarten kann.

Schnittstellen sind wichtig, weil sie unterscheiden, was eine Klasse tut und wie sie es tut. Der Vertrag, der festlegt, was ein Kunde erwarten kann, lässt dem Entwickler die Möglichkeit, ihn nach Belieben umzusetzen, solange er den Vertrag einhält.

und

Eine Schnittstelle ist eine Beschreibung der Aktionen, die ein Objekt ausführen kann. Wenn Sie beispielsweise einen Lichtschalter betätigen, leuchtet das Licht, und es ist Ihnen egal, wie. In der objektorientierten Programmierung ist eine Schnittstelle eine Beschreibung aller Funktionen, die ein Objekt haben muss, um ein "X" zu sein.

und

Ich denke, der schnittstellenbasierte Ansatz ist bedeutend besser. Sie können dann Ihre Abhängigkeiten gut verspotten, und alles ist im Grunde genommen weniger eng miteinander verbunden.

Was ist der Sinn einer Schnittstelle?

Was sind Schnittstellen?

Interface + Extension (Mixin) vs Basisklasse

Angesichts der Tatsache, dass der Zweck von Schnittstellen darin besteht, einen Vertrag zu definieren und Ihre Abhängigkeiten lose miteinander zu verknüpfen, wird UnsupportedOperationExceptionder Zweck nicht durch einige Methoden zunichte gemacht ? Es bedeutet, dass ich nicht mehr weitergegeben werden kann Setund nur noch benutze addAll. Vielmehr muss ich wissen, welche Implementierung von Setmir bestanden wurde, damit ich wissen kann, ob ich verwenden kann addAlloder nicht. Das erscheint mir ziemlich wertlos.

Worum geht es also UnsupportedOperationException? Gleicht es nur den alten Code aus und sie müssen ihre Schnittstellen bereinigen? Oder hat es einen sinnlicheren Zweck, den ich vermisse?

MirroredFate
quelle
Ich weiß nicht, welche JRE Sie verwenden, aber meine Oracle-Version 8 definiert nicht addAllin HashSet. Es verschiebt sich zu der Standardimplementierung, in AbstractCollectionder ganz sicher nicht geworfen wird UnsupportedOperationException.
@Schneemann Du hast recht. Ich habe die Dokumente falsch gelesen. Ich werde meine Frage bearbeiten.
MirroredFate
1
Ich starte gerne Eclipse und schaue mir den Quellcode an. Dabei gehe ich auf Code-Referenzen und -Definitionen ein, um sicherzugehen, dass ich alles richtig gemacht habe. Solange die JRE damit verbunden ist, src.zipfunktioniert sie hervorragend. Es ist hilfreich, genau zu wissen, welchen Code die JRE manchmal ausführt, und sich nicht auf JavaDoc zu beschränken, was etwas ausführlich sein kann.

Antworten:

12

Schauen Sie sich die folgenden Schnittstellen an:

Diese Schnittstellen deklarieren alle Mutationsmethoden als optional. Dies dokumentiert implizit die Tatsache, dass die Collections- Klasse Implementierungen dieser unveränderlichen Schnittstellen zurückgeben kann, dh, dass diese optionalen Mutationsoperationen garantiert fehlschlagen. Gemäß dem Vertrag in JavaDoc müssen jedoch alle Implementierungen dieser Schnittstellen die Leseoperationen zulassen. Dies beinhaltet die "normalen" Implementierungen wie HashSetund LinkedListsowie die unveränderlichen Wrapper inCollections .

Kontrast zu den Queue-Interfaces:

Diese Schnittstelle kann nicht angeben , alle optionalen Operationen: eine Warteschlange, per Definition, wird entwickelt , um Angebot und Umfrage - Elemente in einer FIFO - Weise. Eine unveränderliche Warteschlange ist ungefähr so ​​nützlich wie ein Auto ohne Räder.


Eine häufige Idee, die immer wieder auftaucht, ist eine Vererbungshierarchie, die sowohl veränderbare als auch unveränderliche Objekte enthält. Diese haben jedoch alle Nachteile. Die Komplexität trübt das Wasser, ohne das Problem tatsächlich zu lösen.

  • Eine Hypothese Setkönnte die Leseoperationen haben, und eine Unterschnittstelle MutableSetkönnte die Schreiboperationen haben. Liskov sagt uns, dass a MutableSetdann an alles übergeben werden könnte, was a benötigt Set. Das hört sich zunächst in Ordnung an, aber denken Sie daran, dass eine Methode, die davon ausgeht, dass die zugrunde liegende Menge nicht geändert wird, während des Lesens: Es wäre möglich, dass zwei Threads dieselbe Menge verwenden und die Invariante der Menge verletzen, die sich nicht ändert. Dies kann zu Problemen führen, z. B. wenn eine Methode ein Element zweimal aus der Menge liest und es beim ersten Mal vorhanden ist, beim zweiten Mal jedoch nicht.

  • Setkönnte keine direkten Implementierungen haben, stattdessen MutableSetund ImmutableSetals Subschnittstellen, die dann zum Implementieren von Klassen verwendet werden. Dies hat das gleiche Problem wie oben: An einem bestimmten Punkt in der Hierarchie weist eine Schnittstelle widersprüchliche Invarianten auf. Einer sagt "Diese Menge muss veränderlich sein" und der andere sagt "Diese Menge kann sich nicht ändern".

  • Es könnte zwei völlig getrennte Hierarchien für veränderbare und unveränderliche Datenstrukturen geben. Dies fügt eine Tonne zusätzlicher Komplexität hinzu, was letztendlich nur ein sehr geringer Gewinn ist. Dies hat auch die spezifische Schwäche von Methoden, die sich nicht um die Veränderbarkeit kümmern (z. B. ich möchte nur eine Liste iterieren), müssen nun zwei separate Schnittstellen unterstützen. Da Java statisch typisiert ist, bedeutet dies zusätzliche Methoden zur Behandlung beider Schnittstellenhierarchien.

  • Wir könnten eine einzige Schnittstelle haben und Implementierungen erlauben, Ausnahmen auszulösen, wenn eine Methode darauf nicht anwendbar ist. Dies ist der Weg, den Java eingeschlagen hat und der am sinnvollsten ist. Die Anzahl der Schnittstellen ist auf ein Minimum beschränkt, und es gibt keine Invarianten für die Wandlungsfähigkeit, da die dokumentierte Schnittstelle in keiner Weise die Wandlungsfähigkeit garantiert . Wenn eine Unveränderlichkeitsinvariante erforderlich ist, verwenden Sie die Wrapper in Collections. Wenn eine Methode eine Sammlung nicht ändern muss, ändern Sie sie einfach nicht. Der Nachteil ist, dass eine Methode nicht garantieren kann, dass sich eine Auflistung in einem anderen Thread nicht ändert, wenn sie von außen bereitgestellt wird. Dies ist jedoch ein Problem der aufrufenden Methode (oder ihrer aufrufenden Methode).


Verwandte Lektüre: Warum enthält Java 8 keine unveränderlichen Sammlungen?

Gemeinschaft
quelle
1
Aber wenn Methoden optional sind, welchen Platz haben sie in der Schnittstelle? Sollte es beispielsweise keine separate Schnittstelle geben, die die optionalen Methoden enthält MutableCollection?
MirroredFate
Nein. Es gibt keine Möglichkeit, veränderliche und unveränderliche Objekte in sinnvoller Weise in derselben Hierarchie zu haben. Vor kurzem gab es eine Frage mit einem guten Diagramm, in dem die Komplexität und die Erklärung, warum dies eine schlechte Idee ist, dargestellt wurden. Sie wurde jedoch gestrichen. Vielleicht weiß jemand anderes eine Frage, um dies zu erklären, ich kann nichts finden. Aber ich werde meine Antwort aktualisieren, um ein wenig zu erklären.
Das ist eine breite Aussage über unveränderliche Warteschlangen. Ich benutzte ein nur ein paar Tage vor zu lösen dieses Problem .
Karl Bielefeldt
@Snowman Aber das scheint die veränderlichen und unveränderlichen Objekte als Gegensätze voneinander auszumachen. Ich denke, unveränderliche Objekte sind wirklich nur Objekte, denen die Fähigkeit zur Mutation fehlt. Ehrlich gesagt ist die Art und Weise, wie es jetzt ist, komplexer und durcheinander, weil Sie herausfinden müssen, was eine veränderbare Implementierung ist und was nicht. Es scheint mir, dass der einzige Unterschied zwischen der Zusammenfassung aller Methoden in einer Schnittstelle und der Aufteilung der veränderlichen Methoden in Klarheit besteht.
MirroredFate
@MirroredFate hat meine letzte Bearbeitung gelesen.
2

Es ist im Grunde YAGNI. Alle konkreten Sammlungen in der Standardbibliothek sind veränderbar und implementieren oder erben die optionalen Operationen. Sie interessieren sich nicht für unveränderliche Allzweck-Sammlungen und die große Mehrheit der Java-Entwickler auch nicht. Sie werden keine vollständige Schnittstellenhierarchie nur für unveränderliche Sammlungen erstellen und dann keine Implementierungen einschließen.

Auf der anderen Seite gibt es einige spezielle Werte oder "virtuelle" Sammlungen, die als unveränderlich sehr nützlich sein könnten, wie beispielsweise leere Mengen und nCopies . Es gibt auch unveränderliche Sammlungen von Drittanbietern (wie Scala's), die möglicherweise vorhandenen Java-Code aufrufen möchten, sodass die Möglichkeit für unveränderliche Sammlungen auf die am wenigsten störende Weise offen bleibt.

Karl Bielefeldt
quelle
Ok, das macht Sinn. Es scheint jedoch immer noch, als würde das Problem aus der falschen Richtung angegangen. Warum nicht mit der Definition unveränderlicher Erfassungsschnittstellen beginnen und dann die veränderlichen Schnittstellen für die veränderlichen Erfassungsimplementierungen definieren?
MirroredFate