Zum Sinn von "UnsupportedOperationException"

Status
Nicht offen für weitere Antworten.

Marco13

Top Contributor
Hi

Noch eine allgemeine OOP-Frage, die ich mal von http://www.java-forum.org/de/topic80048_wie-tief-darf-eine-vererbungshierarchie-sein.html abzweige, weil sie damit eigentlich nichts mehr zu tun hat. (Falls ein Mod das anders sieht, kann er die Threads wieder zusammenführen).

Im Interface "List" der Java Collections API werden alle Methoden zusammengefasst, die eine "List" anbietet. Einige dieser Methoden sind gekennzeichnet als "optional operation". Wenn man genau hinsieht, stellt man fest, dass im wesentlichen die Methoden "optional" sind, die die Liste verändern. (Ähnlich ist es bei anderen Collections-Interfaces - List ist nur ein Beispiel).

Wäre es - theoretisch, aus OOP-Design-Sicht - nicht sinnvoller, ein Interface "UnmodifiableList" zu erstellen, das alle Methoden anbietet, die in List jetzt NICHT-Optional sind, und zusätzlich ein Interface "List extends UnmodifiableList", in dem die optionalen Operationen mit drinstehen?

Ein Interface erfüllt ja gerade den Zweck vorzugeben, welche Methoden ein Objekt anbietet, wenn seine Klasse dieses Interface implementiert. Dann DOCH wieder bei einigen Methoden eine "UnsupportedOperationException" zu werfen widerspricht ja eigentlich dem Sinn eines Interfaces....

Beim Beispiel "List" könnte man vielleicht sagen, dass das Altlasten aus Java 1.0 sind. Aber speziell für die Entwicklung neuer APIs stellt sich doch die Frage: Sollte man Interfaces so feingranualar machen, dass man NIE irgendwo eine "UnsupportedOperationException" werfen muss? Welche Argumentation könnte es geben, um zu rechtfertigen, dass man bei einer Methode eine "UnsupportedOperationException" wirft oder werfen darf? (Oder suggestiv gefragt: Warum sollte man diese Exception nicht bei ALLEN Methoden werfen können?)
 

Murray

Top Contributor
Bei List kann man ja noch mit dem "Altlast-Argument" komen; bei java.io.InputStream geht das nicht - dort gab es immer schon markSupported und mark / skip / reset. Schon da habe ich nicht begriffen, wieso man nicht lieber zwischen einer Basisklasse SequentialStream und einer davon abgeleiteten Klassen RadomAccesssStream unterscheidet - ob ein Stream solche Operationen unterstützt, hängt ja wohl ausschließlich am Typ dieses Streams (und nicht etwa an seinem aktuellen Zustand).
 
M

maki

Gast
Beim Beispiel "List" könnte man vielleicht sagen, dass das Altlasten aus Java 1.0 sind.
List ist teil der Collection API, keine Altlasten sondern relativ neu, "UnsupportedOperationException" ist kein Versehen/Designfehler, sondern volle Absicht und gut so :)

Was wäre die Alternative gewesen?
2 Interfaces:
ReadableList
WritableList

Alle Listen (Collections) sind readable, immer.
Writable sind sie zu 99% auch immer(ausser ich will es anders), also nicht wirklich "optional", zumindest im Normalfall.
Dafür die Anzahl der Interfaces in der Collection API verdoppeln? (Collection, List, Set, Map)

Finde die Lösung wie sie ist mit den UnsupportOperationException gut, hält ie Interfaces & deren Hierarchie einfach, einfach zu nutzen, seltene speziallfälle werden auch als solche betrachtet und blähen die API nicht unnötig auf.
 

Murray

Top Contributor
Gast hat gesagt.:
Murray hat gesagt.:
Bei List kann man ja noch mit dem "Altlast-Argument" komen; bei java.io.InputStream geht das nicht
nur dass InputStream älter als List ist ...
Eben - die Collection-Interfaces sind nachträglich dazugekommen und wurden so gebaut, dass die Migration von Vector und Konsorten einfach möglich war - daher gibt es "Altlasten" aus den alten APIs.
 
M

maki

Gast
Eben - die Collection-Interfaces sind nachträglich dazugekommen und wurden so gebaut, dass die Migration von Vector und Konsorten einfach möglich war - daher gibt es "Altlasten" aus den alten APIs.
Nee nee, keine Altlasten, die Collections wurden auch nicht so gebaut um eine Migration zu vereinfachen, schliesslich wurden Vector und Hashtable "retrofitted".
Ob ich ein oder 2 Interfaces implementiere ändert nix an der einfachheit der Migration.
 
G

Gast

Gast
Murray hat gesagt.:
Gast hat gesagt.:
Murray hat gesagt.:
Bei List kann man ja noch mit dem "Altlast-Argument" komen; bei java.io.InputStream geht das nicht
nur dass InputStream älter als List ist ...
Eben - die Collection-Interfaces sind nachträglich dazugekommen und wurden so gebaut, dass die Migration von Vector und Konsorten einfach möglich war - daher gibt es "Altlasten" aus den alten APIs.
Weil List neuer ist besitzt es also Altlasten, im Gegensatz zu InputStream, was schon immer da war. Jo, ergibt Sinn.
Davon abgesehen ist die Migration von Vector kein Grund warum man keine UnmodifiableList hätte einführen können!?
=> Unsinn
 

Murray

Top Contributor
Gast hat gesagt.:
Weil List neuer ist besitzt es also Altlasten, im Gegensatz zu InputStream, was schon immer da war. Jo, ergibt Sinn.
Ja, tatsächlich ergibt das Sinn - nur bei einer nachträglich hinzugekommenen Klasse, bei deren Design man Rücksicht auf bestehenden Code nehmen muss, kann es überhaupt Altlasten geben. Davon ab - das "Altlasten"-Argument stammt nicht von mir, sondern wurde vom TO genannt.

Gast hat gesagt.:
Davon abgesehen ist die Migration von Vector kein Grund warum man keine UnmodifiableList hätte einführen können!?
Habe ich auch nie behauptet, das hätte man sicher machen können. Maki meint, das hätte die Zahl der Interfaces unnötigerweise erhöht und damit das API unübersichtlicher gemacht - das ist vielleicht Geschmackssache, Auf jeden Fall hätte man ja nicht eine ReadableList neben eine WritableList stellen müssen, sondern eine ModifiableList als Ableitung der "normalen" Liste vorsehen können.

Ich könnte mir vorstellen, dass man das nicht getan hat, weil die Tatsache, ob eine Liste änderbar ist oder nicht, möglichweise bei einer Instanz nicht unveränderlich ist - man könnte sich ja vorstellen, dass eine Liste zunächst änderbar ist (irgendwie muss sie ja gefüllt werden) und dann später quasi "versiegelt" wird.

Gast hat gesagt.:
Halte mal den Ball flach
 
M

maki

Gast
Maki meint, das hätte die Zahl der Interfaces unnötigerweise erhöht und damit das API unübersichtlicher gemacht - das ist vielleicht Geschmackssache,
Geschmackssache?
Findest du wirklich? ;)

Interfaces des Collection Frameworks:
Collection
Iterator
List
ListIterator
Map
RandomAccess
Set
SortedMap
SortedSet

Mit einer Unterscheidung readable/writable:
readableCollection
writableCollection
Iterator
readableList
writableList
ListIterator
readableMap
writableMap
RandomAccess
readableSet
writableSet
readableSortedMap
writableSortedMap
readableSortedSortedSet
writableSortedSortedSet

"Geschmackssache" ist nicht der richtige Begriff imho, es richtet sich nach der Anwendung, ohne den Kontext zu berücksichtigen kann man kein gutes Design entwerfen.

Ich benutze Collections (Collection, List, Set...) täglich in der Arbeit, alle sind änderbar, kann mich nicht daran erinnern wann ich eine nicht modifizierbare Collection nutzte.
Dafür die API so "zumüllen" mit Spezialfällen die ich noch nie gebraucht habe?

Also habe ich kein Problem mit der API wie sie ist, im Gegenteil, sie bietet mir was ich brauche bei einem geringen Mass an Komplexität -> gutes Design
 

Marco13

Top Contributor
Ohje - also, es ging jetzt nicht darum, auszudiskutieren, ob das Altlasten sind oder nicht - oder nur sekundär, in dem Sinne, dass ich mich frage, ob das eine bewußte Designentscheidung war, oder nicht.

Ich finde es aus Sicht des Klassendesigns und der Stuktur eigentlich SEHR unschön.

maki hat gesagt.:
Was wäre die Alternative gewesen?
2 Interfaces:
ReadableList
WritableList

Alle Listen (Collections) sind readable, immer.
Writable sind sie zu 99% auch immer(ausser ich will es anders), also nicht wirklich "optional", zumindest im Normalfall.
Dafür die Anzahl der Interfaces in der Collection API verdoppeln? (Collection, List, Set, Map)

Finde die Lösung wie sie ist mit den UnsupportOperationException gut, hält ie Interfaces & deren Hierarchie einfach, einfach zu nutzen, seltene speziallfälle werden auch als solche betrachtet und blähen die API nicht unnötig auf.

Naja, ich finde eben nicht, dass das so "unnötig" wäre. Es gibt dann zwar mehr interfaces, aber damit beschreiben die Interfaces eben genauer das, was mti den jeweiligen Objekten gemacht werden kann. (Und nochmal: Genau dafür sind Interfaces ja da!). Und "nicht-Veränderbarkeit" ist IMHO alles andere als ein "seltener Spezialfall". Genaugenommen tritt der Wunsch danach so häufig auf, dass er sich schon als das Design-Pattern "Immutable" manifestiert. Speziell dazu auch:

Murray hat gesagt.:
Ich könnte mir vorstellen, dass man das nicht getan hat, weil die Tatsache, ob eine Liste änderbar ist oder nicht, möglichweise bei einer Instanz nicht unveränderlich ist - man könnte sich ja vorstellen, dass eine Liste zunächst änderbar ist (irgendwie muss sie ja gefüllt werden) und dann später quasi "versiegelt" wird.

Dafür gibt es ja jetzt auch Mechanismen - die ... eben einer der Punkte sind, die mir Bauchschmerzen machen: Mit Collections.unmodifiableList(list) kann man eine List "einwickeln", so dass alle nachfolgenden Veränderungsversuche eine UnsupportedOperationException werfen. Das kann man z.B. verwenden, wenn man Daten in einer List speichert, und diese List nach draußen geben will, aber (im Zuge von Kapselung usw) nicht zulassen will, dass jemand "von außen" Mist mit dieser Liste macht
Code:
class SomeClass
{
    private List data = ...
    public List getData() 
    { 
        return Collections.unmodifiableList(data); 
    }
}
Dass diese Liste nicht veränderbar ist, sieht man von außen nicht. Das muss im Kommentar stehen. Wenn man die Interfaces feiner aufteilen würde, wäre das gleiche ohne die "Krücke" der Collections.unmodifiableList möglich:
Code:
class SomeClass
{
    private List data = ... // Intern als veränderbare List bekannt

    public UnmodifiableList getData()  // Nach außen wird nur die unveränderbare Ansicht gegeben
    {
        return data;
    }
}
Das wäre von der Stuktur, von der Semantik her einfach schöner und suberer getypt. Im Moment ist es ja so, dass man bei einer List nie wissen kann, ob man sie verändern kann oder nicht.

Wieder mal ein suggestives Beispiel, um die Grenzfälle abzutasten:
- Im Collection-Interace gibt es die Methode "iterator", die einen Iterator liefert
- Im List-Interface gibt es die Methode "listIterator", die einen ListIterator liefert
Warum packt man nicht auch die Methode "listIterator" in das Collection-Interface, und läßt "Sets" dort eine UnsupportedOperationException werfen? Ja, das wäre häßlich - rein semantisch und """typtheoretisch""" aber das gleiche, wie die fehlende Unterscheidung zwischen einer veränderbaren und einer unveränderbaren List.
Und konsequent weitergeführt hieße das ja: Im Interface "Collection" stehen ALLE Methoden, die von IRGENDEINER Collection angeboten werden. Die meisten Implementierungen werfen bei den meisten Operationen eine UnsupportedOperationException - aber dafür hat man eine schön übersichtliche API, die nur aus einem Interface besteht :autsch: :wink:

Es geht also unter anderem um die Frage, was ein Argument dafür sein kann, dass man bei "sowas wie der List" Dinge vermischt, die eigentlich in zwei aufeinander aufbauende Interfaces gehören würden. Man könnte das ganze ja wunderschön hierarchisch aufbauen
Code:
interface UnmodifiableList
    get(int);
    size();
    iterator();
    
interface List extends UnmodifiableList
    add(int);
    remove(int);

Warum wurstet man diese Dinge jetzt in EIN Interface, wenn das zur Folge hat, dass man den unteren Teil der Hierarchie durch UnsupportedOperationExceptions "künstlich wegschneiden" muss, wenn doch die Lösung mit der Hierarchie in diesem Sinne vermutlich auf triviale Weise möglich, auf jeden Fall aber sauberer und typsicherer wäre?


EDIT: Noch @maki zu deinem letzen Beitrag: Es gäbe dann ggf. nicht die Unterscheidung zwischen "Readable" und "Writable". Stattdessen gäbe es immernoch die Interfaces, die du auch aufgelistet hast. Nur würde z.B. List (wie oben angedeutet) von "UnmodifiableList" erben (so, wie sie jetzt ja schon von "Collection" erbt). Ab einer bestimmter Hierarchiestufe würde man also keinen Unterschied mehr sehen. Man hätte aber die höhere Flexibilität dadurch, dass man sie - z.B. wie bei dem Beispiel mit dem "getData" oben - auf die höhere Hierarchiestufe beziehen könnte.
 

Landei

Top Contributor
Code:
interface UnmodifiableList
    get(int);
    size();
    iterator();
   
interface List extends UnmodifiableList
    add(int);
    remove(int);

Das geht schon in die richtige Richtung, aaaber:
Es ist durchaus sinnvoll, dass eine UnmodifiableList add oder remove implementiert, es muss dann nur eine neue Instance zurückliefern:
Code:
   interface UnmodifiableList<T> {
       UnmodifiableList<T> add(T t);
       UnmodifiableList<T> remove(T t);
       UnmodifiableList<T> remove(int index);
       ...  
   }

   UnmodifiableList<T> list =...

   for(T t : sonstwoher) {
       list = list.add(t); 
   }
 
M

maki

Gast
Ich finde es aus Sicht des Klassendesigns und der Stuktur eigentlich SEHR unschön.
Wenn eine API auf dem "Zeichenbrett" entwirft sieht die eben anders aus, als wenn sich die API sinnvoll an der Praxis orientiert.

Naja, ich finde eben nicht, dass das so "unnötig" wäre. Es gibt dann zwar mehr interfaces, aber damit beschreiben die Interfaces eben genauer das, was mti den jeweiligen Objekten gemacht werden kann. (Und nochmal: Genau dafür sind Interfaces ja da!).
API design ist mehr als nur eine Struktur am Zeichenbrett entwerfen und prüfen ob diese mit den Grundregeln der OO übereinstimmt, die "usability" ist genausowichtig wenn nicht wichtiger, und alles hängt davon ab was man wirklich braucht, nicht was eine "schöne" Hierarchie abgibt.

Und "nicht-Veränderbarkeit" ist IMHO alles andere als ein "seltener Spezialfall". Genaugenommen tritt der Wunsch danach so häufig auf, dass er sich schon als das Design-Pattern "Immutable" manifestiert.
Das immutable Pattern sieht man immer nur im Zusammenhang mit sog. "ValueObjects", wie Landei berats sagte würde man einfach neue ValueObjects zurückgeben lassen von immutables, wäre aber ziemlich sinnfrei (zumindest in 99% der Anwendungsfälle) bei Collections.
Der Wunsch nach "immutable Collections" ist also sehr selten, oder würdest du nicht sagen? ;)

Wenn ich also so gut wie nie eine unmodifiable List brauche, warum sollte ich die ganze API mit diesem sehr seltenen Spezialfall überladen?

Wie oft hast du sie denn schon gebraucht?
Wäre es klug die API nach solchen Spezialfällen auszurichten?

Das macht eben den Unterschied zwischen Kopfgeburten die auf dem Papier "sauberer" aussehen aber in der Praxis unnötig aufgeblasen sind:
Die Anwendung der API, der Kontext.
 

Marco13

Top Contributor
@Landei: Sowas würde sich aber nur schwer in eine API gießen lassen.
Es ist durchaus sinnvoll, dass eine UnmodifiableList add oder remove implementiert, es muss dann nur eine neue Instance zurückliefern:
Wenn die Methodensignatur für eine List und für eine UnmodifiableList unterschiedlich sein sollte (und bei Unmodifiable*-Collections der Typ zusätzlich noch von der Klasse/dem Interface abhängt, wo die Methode deklariert ist) kann ich mir kaum vorstellen, wie man da in bezug auf die Klassen/Interfacestruktur ein ... "stimmiges" Konzept draus machen sollte... (Ob das in der Praxis sinnvoll wäre, wenn bei jedem Versuch einer Veränderung einer (potentiell riesigen) Liste eine Kopie davon angelegt werden würde, weiß ich auch nicht ... aber das ist eine andere Frage)

@maki: Ich finde, dass "unmodifiable collections" alles andere als ein Spezialfall sind. Jetzt sind die halt mit der Krücke Collections.unmodifiable* unterstützt. Teilweise wurde der Wunsch nach einer besseren "Erkennbarkeit" von Unveränderbarkeit schon antizipiert: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/Unmodifiable.html Aber eben immer nur nachträglich reingemurkst, mit "instanceof" abfragbar, weil aufbauend auf der (IMHO in diesem Sinne nicht optimalen) Struktur, bei der man scheinbar jede Collection verändern darf. Ich finde, Unveränderbarkeit ist gerade bei Collections etwas, was für eine saubere Kapselung sehr wichtig (oder zumindest praktisch) wäre.

Wäre es klug die API nach solchen Spezialfällen auszurichten?

Das ist genau die Frage. Jeder, der eine List "in der Hand" hat, geht davon aus, dass er die Methoden darauf aufrufen kann, die im Interface stehen. Wenn er dann eine Methode aufruft, und eine "UnsupportedOperationException" bekommt, schaut er mit dem Ofenrohr ins Gebirge, und weiß nicht, was er damit machen soll. Ich finde einfach, dass das Paradigma "Compilezeitfehler sind besser als Laufzeitfehler" seine Berechtigung hat, und finde die Idee, sich ein Interface zu sparen, und damit zur Laufzeit eine Exception (eine Ausnahme - also ein Spezialfall im Programmfluß :wink: ) zu generieren, in dieser Hinsicht ziemlich fragwürdig ist... :?

Ganz pragmatisch gefragt: Solle man sich, wenn man seine eigene API entwirft, Interfaces sparen, und stattdessen UnsupportedOperationExceptions werfen? Irgendwie ... behagt mir das nicht. Vielleicht wird die API übersichtlicher, aber nicht notwendigerweise einfacher zu verwenden, und bestimmt nicht ... "sicherer" oder "komfortabler". Vielleicht hängt es auch nur davon ab, ob man "Übersichtlichkeit" höher bewertet als "Typtheoretische Schönheit" (oder so) - wobei die Übersichtlichkeit auch nur durch die geringere Anzahl der Interfaces höher werden würde - eine klare Trennung zwischen "Methoden zum Lesen" und "Methoden zum schreiben" würde einen ja nun auch nicht komplett verwirren...
 
M

maki

Gast
@maki: Ich finde, dass "unmodifiable collections" alles andere als ein Spezialfall sind. Jetzt sind die halt mit der Krücke Collections.unmodifiable* unterstützt. Teilweise wurde der Wunsch nach einer besseren "Erkennbarkeit" von Unveränderbarkeit schon antizipiert: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/Unmodifiable.html Aber eben immer nur nachträglich reingemurkst, mit "instanceof" abfragbar, weil aufbauend auf der (IMHO in diesem Sinne nicht optimalen) Struktur, bei der man scheinbar jede Collection verändern darf. Ich finde, Unveränderbarkeit ist gerade bei Collections etwas, was für eine saubere Kapselung sehr wichtig (oder zumindest praktisch) wäre.
Aus meiner Sicht: Ich hab noch nie eine unmodifizierbare Collection gebraucht.
Klar gibt es immer wenn der mal eine braucht, aber wie viele?

Nehmen wir mal an ich hätte ein Objekt welches eine List als Member hat, mit einem getter dafür, Möglichkeiten:
1. Der getter gibt direkt eine Referenz zurück, in den meisten Fällen inakzeptabel, da die Kapselun durchbrochen wird (wenn ich selber den Aufrufer schreibe ist das halb so wild).

2. Der getter gibt eine Kopie der Liste zurück, kostet ein wenig Laufzeit, was aber zu vernachlässigen ist, dafür kann der Aufrufer alles mit der Liste machen, zB. sortieren & ändern, kommt imho relativ häufig vor.

3. Der getter gibt eine UnmodifiableList zurück, wenn der Aufrufer irgendetwas anderes machen muss als nur durchiterieren, muss er sich selbst um Punkt 2 kümmern.

Persönlich finde ich die commons-collections nett für ein paar Dinge, ist mir aber viel zu Groß für den alltäglichen bedarf ;)
Den im Alltag reicht die Collection API vollkommen aus.

Sehen wir mal den Kontext der Collections API:
Josh Bloch musste die Collection API entwerfen, um der Java Sprache ein neues Mittel zu geben, sie ist also seit 1.2 für immer (?) Teil von Java.
Sprachen die etwas für jeden Spezialfall bieten werden schnell unübersichtlich, generationen von Entwicklern müssen sich damit auseinandersetzen, jedes Interface mehr bedeutet mehr Aufwand, mehr Komplexität.

Weniger ist mehr, wenn man etwas spezielles braucht kann man es doch selbst implementieren (siehe commons-collections).
Flexibilität fördert nunmal Komplexität, und letzteres verursacht häufig genug Probleme, einmal weil man sie schneller missversteht bzw. überwältigt ist wenn einem die Übersicht fehlt, was einen dann wieder weniger flexibel macht, d.h. der Vorteil den man erreichen wollte fehlt einem wieder weil man es zu gut gemeint hat.

Oder einfach: KISS ;)

Ein Bespiel was imho zu Komplex geworden ist die JodaTime API:
http://joda-time.sourceforge.net/api-release/index.html

Immutables wären da besser gewesen imho, dann brauche ich nicht mehr zwischen readable/writable zu Unterscheiden.
 

Tobias

Top Contributor
IMHO hat Marco durchaus recht. Maki allerdings auch. Eine Typhierachie wie

Code:
interface ReadableList {
    get(int);
    size();
}

interface ModifiableList {
    add(int);
    remove(int);
}

interface List extends ReadableList, ModifiableList {
}

würde die gewünschte Ausdrucksstärke bei gleichzeitiger Einfachheit der API zur Verfügung stellen (das hat Marco aber schon gesagt, oder zumindest ist es so bei mir angekommen). Wir müssen uns wohl einfach damit abfinden, dass das JDK nicht supersauber designt ist (Bestes Beispiel: java.util.Stack *grusel*). Für meine Zwecke reicht es aber mehr als dicke aus ;).

mpG
Tobias
 
M

maki

Gast
würde die gewünschte Ausdrucksstärke bei gleichzeitiger Einfachheit der API zur Verfügung stellen
Eben nicht, wann hättest du das ReadableList benutzt?
Einen konkreten Fall bitte ;)

Wir müssen uns wohl einfach damit abfinden, dass das JDK nicht supersauber designt ist (Bestes Beispiel: java.util.Stack *grusel*).
Stack gehört zu den Altlasten, genauso wie Vector und Hashtable.
 

Tobias

Top Contributor
Eben überall da, wo ich sonst mit Collections#unmodifiableList() rumhampel, was praktisch überall da passiert, wo ich den internen Status eines Objekts nach außen geben muss, ohne dem Klienten die Möglichkeit geben zu wollen, dieses Status zu verändern ohne die API des Objekts zu benutzen. Das ist nicht unbedingt ein Fall, der ständig auftritt, aber nichtsdestotrotz eine hinreichende Häufigkeit in meinem Programmieralltag hat.

EDIT: Ich möchte nochmals betonen, dass ich das Collection-Framework für eine gute Bibliothek halte und mit diesen "Detailunzulänglichkeiten" in bestimmten Sonderfällen gut leben kann - meine eigenen Programme sind schließlich mit absoluter Sicherheit in vielen Punkten schlechter. Aber betrachtet als Platzhalter für eine Bibliothek, die Marco entwickeln möchte, kann ich seine Argumente sehr gut nachvollziehen.
Deshalb verstehe ich aber auch Makis Standpunkt, dass man nicht alle Sonderfälle erschlagen kann, ohne die Komplexität der Bibliothek ins Unendliche zu treiben, sehr gut. Ich bin nur nicht der Meinung, das eine nicht-modifizierbare Liste ein so seltener Sonderfall ist, dass man ihn nicht hätte beachten _können_.

Toll, jetzt ist mein Edit länger als der eigentlich Post :autsch: ...
 

Landei

Top Contributor
"ValueObjects", wie Landei berats sagte würde man einfach neue ValueObjects zurückgeben lassen von immutables, wäre aber ziemlich sinnfrei (zumindest in 99% der Anwendungsfälle) bei Collections.

@Landei: Sowas würde sich aber nur schwer in eine API gießen lassen.

I beg to differ :-D

Ich kenne nämlich eine entsprechende API, die das exakt so macht, nämlich die immutable Collections in Scala. Natürlich muss man bei den immutable Klassen auf die Performance achten, aber da gibt es durchaus Möglichkeiten.

Und wem Scala zu weit hergeholt ist, schaue sich die Functional Java API für Collections an: http://functionaljava.org/
 

Marco13

Top Contributor
@maki: Das FunctionalJava sieht ganz interessant aus - aber ist wohl auch weniger für die alltägliche Arbeit eines "allerwelts-Entwicklers" gedacht (Methoden wie http://functionaljava.googlecode.com/svn/artifacts/2.17/javadoc/fj/data/List.html#bind(fj.data.List, fj.data.List, fj.data.List, fj.data.List, fj.data.List, fj.data.List, fj.data.List, fj.F) würde ich in einer "normalen API" nur ungern anbieten.... :wink: )

@Tobias: Du das nochmal genau richtig zusammengefasst. Mich "stört" dieses fehlende Interface im Fall der Collections-API auch nicht. Es geht ja tatsächlich eher um die Frage, inwiefern man sich an diesem Schema orientieren sollte. (Deiner Aussage "Wir müssen uns wohl einfach damit abfinden, dass das JDK nicht supersauber designt ist " nach findet du wohl auch, dass man das eher nicht tun sollte...)

Ich bin/war mir eben nicht sicher, ob die Sache mit der UnsupportedOperationException eine bewußte und in bezug auf OOP/Softwareengineering und Typstrukturen begründete Designentscheidung war, oder nur der Versuch, die API "einfacher" wirken zu lassen unter (möglicherweise zähneknirschender) Inkaufnahme der "Inkonsistenz" die das "raus-UNsupporten" eines Teils eines Interfaces aus theoretischer, Softwarearchitektonischer Sicht IMHO bedeutet. Und WENN das eine bewußte Entscheidung war, wüßte ich gerne, wie sie begründet ist, und nach welchen Kriterien man entscheiden sollte, wie "feingranular" man seine Interfaces aufbauen sollte, bzw. an welchen Stellen man bei der (potentiell ungerechtfertigten) Zusammenfassung mehrerer Interfaces eine UnsupportedOperationException verwenden sollte, oder (wenn das (wie ich finde) nirgendwo der Fall ist) an welchen Stellen so eine Zusammenfassung und die Verwendung der Exception zumindest "vertretbar" ist...
 

Tobias

Top Contributor
Die entscheidende Frage ist, "Wie oft tritt dieser Fall auf?" und "Wie sehr beeinflußt eine Lösung mit einem zusätzlichen Interface die Handhabung meiner Bibliothek?". Ich kann mir durchaus vorstellen, das die Collection-Framework-Entwickler sich auf einen ähnlichen Standpunkt wie Maki gestellt haben und gesagt haben: "Der Fall tritt zwar ab und zu auf, aber nicht so häufig, dass wir unsere Bibliothek mit zusätzlichen Interfaces belasten und dadurch den Zugang für Anfänger erschweren. Die Sonderfälle erschlagen wir durch die nicht 100%-ige, aber dennoch benutzbare Methode, eine Exception zu werfen." Eine Lösung, mit der alle leben können und die nur auf "akademischem Level" angezweifelt wird. Kurz: Ich denke, hier regiert der Pragmatismus.

Für das Problem in deiner eigenen Entwicklung, das dich zum Eröffnen dieses Threads gebracht hat: Überdenke doch einfach, wie wichtig der Anwendungsfall ist, den du durch ein zusätzliches Interface lösen möchtest und ob er es wirklich wert ist, die Zugänglichkeit deiner Bibliothek durch die zusätzliche Komplexität zu verringern. Eine UnsupportedOperationException ist sicher nicht schön, aber manchmal halt das kleinere Übel.
 
M

maki

Gast
Ich bin/war mir eben nicht sicher, ob die Sache mit der UnsupportedOperationException eine bewußte und in bezug auf OOP/Softwareengineering und Typstrukturen begründete Designentscheidung war, oder nur der Versuch, die API "einfacher" wirken zu lassen unter (möglicherweise zähneknirschender) Inkaufnahme der "Inkonsistenz" die das "raus-UNsupporten" eines Teils eines Interfaces aus theoretischer, Softwarearchitektonischer Sicht IMHO bedeutet.
Verstehe nicht ganz warum du das als widerspruch siehst, kompromisse muss man schlieslich immer eingehen.

Blochs Entscheidung wurde oft diskutiert & erklärt, zB.:
16.2 Optional Methods

The collections API allows a class to claim to implement a collections interface with-
out implementing all of its methods. For example, all the mutators of List are specified
as optional. This means you can implement a class that satisfies the List specification,
but which throws an UnsupportedOperationException whenever you call a mutator,
such as add.

This intentional weakening of the List specification is problematic, because it means
that if you’re writing some code that receives a list, you don’t know, in the absence of
additional information about the list, whether it will support add.

But without this notion of optional operations, you’d have to declare a separate inter-
face ImmutableList. These interfaces would proliferate. Sometimes, we want to require
some mutators but not others. For example, the keySet method of HashMap returns a
Set containing the keys of the map. This set is a view: deleting a key from the set caus-
es a key and its associated value to be deleted from the map. So remove is supported.
But add is unsupported, since you can’t add a key to a map without an associated value.

So the use of optional operations is a reasonable engineering judgment. It means less
compile-time checking, but it reduces the number of interfaces.
http://www.ocw.cn/NR/rdonlyres/Elec...4-7EB9-45B0-B44F-A571AD88A4DE/0/lecture16.pdf
 

Marco13

Top Contributor
Ja, ich hatte (teilweise bewußt) erstmal auf eine Websuche verzichtet. Wenn man sucht, findet man sofort den passenden FAQ-Eintrag: Java Collections API Design FAQ: Why don't you support immutability directly in the core collection interfaces...?

Die Rechtfertigung ist dort die "kombinatorische Explosion", die mir auch einleuchtet, und der ich in gewisser Hinischt auch gegenüberstehe. Aber irgendwie will ich mich nicht so recht damit abfinden :? Theoretisch ist eine saubere Typisierung möglich, aber wenn sie dann umgesetzt werden soll, kommt man zu dem Schluss, dass sie zu komplex ist, um praktikabel zu sein. Mir gefällt es einfach nicht, vor die Wahl gestellt zu sein: Entweder ein unsauberes Typsystem, bei dem ein Theoretiker nur verständnislos den Kopf schütteln würde, oder eine komplexe API, bei der ein Praktiker nur verständnislos den Kopf schütteln würde :(

Eine Websuche liefert auch Lösungsansätze: http://www.cs.uoregon.edu/Activities/GRF/slides_new/07Mar06-shanshanhuang.pdf Sieht IMHO ganz hübsch aus, ist aber (noch?!) nicht Teil der Sprache. Naja. Vielleicht ja in Java 8 :roll:
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen

Ähnliche Java Themen

Neue Themen


Oben