UnsupportedOperationException w interfejsach środowiska kolekcji java

12

Przeglądając Framework kolekcji Java, zauważyłem, że sporo interfejsów ma komentarz (optional operation). Te metody pozwalają na implementację klas, UnsupportedOperationExceptionjeśli nie chcą po prostu implementować tej metody.

Przykładem tego jest addAllmetoda w Set Interface.

Teraz, jak stwierdzono w tej serii pytań, interfejsy są definiującą umową dotyczącą tego, czego może oczekiwać użycie.

Interfejsy są ważne, ponieważ oddzielają to, co robi klasa od tego, jak to robi. Umowa określająca, czego może oczekiwać klient, pozostawia deweloperowi swobodę wdrażania go w dowolny sposób, pod warunkiem przestrzegania umowy.

i

Interfejs to opis działań, które może wykonywać obiekt ... na przykład po naciśnięciu przełącznika światła światło się zapala, nie obchodzi cię, jak to działa. W programowaniu obiektowym interfejs to opis wszystkich funkcji, które musi posiadać obiekt, aby mógł być „X”.

i

Myślę, że podejście oparte na interfejsie jest znacznie ładniejsze. Następnie możesz ładnie wyśmiewać swoje zależności i wszystko jest zasadniczo mniej ściśle powiązane.

Jaki jest sens interfejsu?

Co to są interfejsy?

Interfejs + rozszerzenie (mixin) vs klasa podstawowa

Biorąc pod uwagę, że celem interfejsów jest zdefiniowanie kontraktu i luźne powiązanie twoich zależności, czy posiadanie niektórych metod nie jest UnsupportedOperationExceptionrodzajem porażki? Oznacza to, że nie mogę już przejść Seti po prostu użyć addAll. Muszę raczej wiedzieć, jaką implementację Setprzeszedłem, więc mogę wiedzieć, czy mogę użyć, addAllczy nie. To wydaje mi się dość bezwartościowe.

Jaki jest więc sens UnsupportedOperationException? Czy to po prostu rekompensowanie starszego kodu i muszą wyczyścić swoje interfejsy? Czy może ma sens bardziej sensowny, że tęsknię?

MirroredFate
źródło
Nie wiem, którego środowiska JRE używasz, ale moja wersja Oracle 8 nie definiuje się addAllw HashSet. Odsyła do domyślnej implementacji, w AbstractCollectionktórej z pewnością nie rzuca UnsupportedOperationException.
@Snowman Masz rację. Brakuje mi dokumentów. Zmienię moje pytanie.
MirroredFate,
1
Lubię uruchamiać Eclipse i patrzeć na kod źródłowy, podskakując wokół referencji i definicji kodu, aby upewnić się, że mam rację. Tak długo, jak JRE jest z src.zipnim połączone , działa świetnie. Pomaga dokładnie wiedzieć, jaki kod JRE czasami uruchamia, i nie odsuwać się do JavaDoc, który może być nieco gadatliwy.

Odpowiedzi:

12

Spójrz na następujące interfejsy:

Wszystkie te interfejsy deklarują metody mutacji jako opcjonalne. Jest to domyślnie dokumentujące fakt, że klasa Kolekcje może zwrócić implementacje niezmiennych interfejsów: to znaczy, że te opcjonalne operacje mutacji na pewno się nie powiodą. Jednak zgodnie z umową w JavaDoc wszystkie implementacje tych interfejsów muszą umożliwiać operacje odczytu. Obejmuje to „normalne” implementacje, takie jak HashSeti LinkedListniezmienne opakowania w Collections.

Porównaj z interfejsami kolejki:

Interfejsy te nie określają żadnych operacji opcjonalnych: kolejka z definicji jest zaprojektowana do oferowania i odpytywania elementów w sposób FIFO. Niezmienna kolejka jest tak samo przydatna jak samochód bez kół.


Jednym z powszechnych pomysłów, który pojawia się wielokrotnie, jest hierarchia dziedziczenia obejmująca zarówno obiekty zmienne, jak i niezmienne. Wszystkie te mają jednak wady. Złożoność moczy wody, nie rozwiązując problemu.

  • Hipotetyczne Setmogą mieć operacje odczytu, a podinterface MutableSetmoże mieć operacje zapisu. Liskov mówi nam, że MutableSetmożna następnie przekazać wszystko, co potrzebuje Set. Na początku brzmi to dobrze, ale rozważ metodę, która oczekuje, że podstawowy zestaw nie zostanie zmodyfikowany podczas odczytu: możliwe byłoby, aby dwa wątki użyły tego samego zestawu i naruszyłyby niezmienność zestawu, który się nie zmienia. Może to powodować problem, np. Jeśli metoda odczytuje element ze zbioru dwa razy i jest tam za pierwszym razem, ale nie za drugim razem.

  • Setmoże nie mieć bezpośrednich implementacji, zamiast tego ma MutableSeti ImmutableSetjako podinterfejsy, które są następnie wykorzystywane do implementacji klas. Ma to ten sam problem, co powyżej: w pewnym momencie w hierarchii interfejs ma niezmienniki powodujące konflikt. Jeden mówi „ten zestaw musi być modyfikowalny”, a drugi mówi „ten zestaw nie może się zmienić”.

  • Mogą istnieć dwie całkowicie odrębne hierarchie dla zmiennych i niezmiennych struktur danych. Dodaje to mnóstwo dodatkowej złożoności, co w rezultacie daje bardzo niewielki zysk. Ma to również swoistą słabość metod, które nie dbają o zmienność (np. Chcę tylko iterować listę), musi teraz obsługiwać dwa oddzielne interfejsy. Ponieważ Java jest typowana statycznie, oznacza to dodatkowe metody obsługi obu hierarchii interfejsów.

  • Możemy mieć jeden interfejs i zezwalać implementacjom na zgłaszanie wyjątków, jeśli metoda nie ma do niego zastosowania. Jest to droga, którą wybrała Java, i ma to największy sens. Liczba interfejsów jest ograniczona do minimum i nie ma niezmienników zmienności, ponieważ udokumentowany interfejs nie daje żadnej gwarancji na zmienność w żaden sposób . Jeśli wymagany jest niezmienność niezmienności, użyj opakowań w Collections. Jeśli metoda nie musi zmieniać kolekcji, po prostu jej nie zmieniaj. Wadą jest to, że metoda nie może zagwarantować, że kolekcja nie zmieni się w innym wątku, jeśli zostanie podana kolekcja z zewnątrz, ale to i tak dotyczy metody wywołującej (lub jej metody wywoływania).


Literatura pokrewna: Dlaczego Java 8 nie zawiera niezmiennych kolekcji?

Społeczność
źródło
1
Ale jeśli metody są opcjonalne, jakie mają one miejsce w interfejsie? Czy nie powinien istnieć oddzielny interfejs zawierający na przykład opcjonalne metody MutableCollection?
MirroredFate
Nie. Nie ma możliwości, aby zmienne i niezmienne obiekty w tej samej hierarchii w jakikolwiek znaczący sposób. Pojawiło się ostatnie pytanie, które miało dobry schemat pokazujący złożoność i wyjaśnienie, dlaczego jest to zły pomysł, ale zostało usunięte. Może ktoś wie o pytaniu, które mogłoby to wyjaśnić, nie mogę nic znaleźć. Ale zaktualizuję swoją odpowiedź, aby trochę wyjaśnić.
To rodzaj szerokiego stwierdzenia o niezmiennych kolejkach. Użyłem jednego zaledwie kilka dni temu, aby rozwiązać ten problem .
Karl Bielefeldt,
@Snowman Ale wydaje się, że te zmienne i niezmienne obiekty są wzajemnie przeciwieństwami. Myślę, że niezmienne obiekty to tak naprawdę obiekty pozbawione zdolności do mutacji. Szczerze mówiąc, sposób, w jaki jest teraz, jest bardziej złożony i zagmatwany, ponieważ musisz dowiedzieć się, co jest zmienną implementacją, a co nie. Wydaje mi się, że jedyną różnicą między umieszczeniem wszystkich metod w jednym interfejsie, a nie rozłożeniem metod zmiennych, jest przejrzystość.
MirroredFate,
@MirroredFate przeczytaj moją ostatnią edycję.
2

Zasadniczo jest to YAGNI. Wszystkie konkretne kolekcje w standardowej bibliotece można modyfikować, implementując lub dziedzicząc operacje opcjonalne. Nie dbają o niezmienne kolekcje ogólnego przeznaczenia, a także ogromna większość programistów Java. Nie zamierzają tworzyć całej hierarchii interfejsów tylko dla niezmiennych kolekcji, a następnie nie będą zawierać żadnych implementacji.

Z drugiej strony istnieje kilka wartości specjalnego przeznaczenia lub „wirtualnych” kolekcji, które mogą być bardzo przydatne jako niezmienne, takie jak pusty zestaw i nCopies . Ponadto istnieją niezmienne kolekcje innych firm (takie jak Scala), które mogą chcieć wywoływać istniejący kod Java, więc pozostawiono możliwość otwierania niezmiennych kolekcji w najmniej zakłócający sposób.

Karl Bielefeldt
źródło
Ok, to ma sens. Nadal wydaje się, że podchodzi do problemu ze złego kierunku. Dlaczego nie zacząć od zdefiniowania niezmiennych interfejsów kolekcji, a następnie zdefiniować mutowalne interfejsy dla implementacji mutable kolekcji?
MirroredFate,