Zaskoczył mnie fakt, że Map<?,?>
nie jest to plik Collection<?>
.
Pomyślałem, że miałoby to dużo sensu, gdyby zostało to zadeklarowane jako takie:
public interface Map<K,V> extends Collection<Map.Entry<K,V>>
W końcu a Map<K,V>
to zbiór Map.Entry<K,V>
, prawda?
Czy jest więc dobry powód, dla którego nie jest zaimplementowany jako taki?
Dziękuję Cletusowi za najbardziej autorytatywną odpowiedź, ale wciąż zastanawiam się, dlaczego, jeśli już możesz zobaczyć Map<K,V>
as Set<Map.Entries<K,V>>
(via entrySet()
), zamiast tego nie tylko rozszerza ten interfejs.
Jeśli a
Map
to aCollection
, jakie są elementy? Jedyną rozsądną odpowiedzią jest „Pary klucz-wartość”
Dokładnie, interface Map<K,V> extends Set<Map.Entry<K,V>>
byłoby świetnie!
ale zapewnia to bardzo ograniczoną (i niezbyt użyteczną)
Map
abstrakcję.
Ale jeśli tak jest, to dlaczego jest entrySet
określony przez interfejs? To musi być jakoś przydatne (i myślę, że łatwo jest polemizować o takie stanowisko!).
Nie możesz zapytać, na jaką wartość jest przypisany dany klucz, ani nie możesz usunąć wpisu dla danego klucza, nie wiedząc, na jaką wartość jest mapowany.
Nie mówię, że to wszystko Map
! Może i powinien zachować wszystkie inne metody (z wyjątkiem tej entrySet
, która jest teraz zbędna)!
źródło
Odpowiedzi:
Z często zadawanych pytań dotyczących projektowania interfejsu API kolekcji Java :
Aktualizacja: myślę, że cytat odpowiada na większość pytań. Warto podkreślić, że zbiór haseł nie jest abstrakcją szczególnie przydatną. Na przykład:
pozwoliłoby:
(zakładając
entry()
metodę, która tworzyMap.Entry
instancję)Map
wymagają unikalnych kluczy, więc naruszałoby to. Lub jeśli narzucisz unikalne klucze na jedenSet
z wpisów, nie jest to tak naprawdęSet
w ogólnym sensie. JestSet
z dalszymi ograniczeniami.Prawdopodobnie można powiedzieć, że związek
equals()
/hashCode()
dlaMap.Entry
był wyłącznie kluczowy, ale nawet to ma problemy. Co ważniejsze, czy naprawdę dodaje jakąś wartość? Może się okazać, że ta abstrakcja się załamie, gdy zaczniesz patrzeć na narożne przypadki.Warto zauważyć, że
HashSet
faktycznie jest zaimplementowany jako aHashMap
, a nie na odwrót. To jest tylko szczegół dotyczący implementacji, ale mimo to jest interesujący.Głównym powodem
entrySet()
istnienia jest uproszczenie przechodzenia, aby nie trzeba było przechodzić przez klucze, a następnie wyszukiwać klucza. Nie traktuj tego jako dowodu prima facie, że aMap
powinno być aSet
z wpisów (imho).źródło
entrySet()
obsługujeremove
, podczas gdy nie obsługujeadd
(prawdopodobnie wyrzucaUnsupportedException
). Więc rozumiem twój punkt widzenia, ale z drugiej strony widzę także punkt PO ... (Moja własna opinia jest taka, jak podano w mojej własnej odpowiedzi ..)add
mogą być obsługiwane tak samo, jakentrySet()
widok: zezwalaj na niektóre operacje, a nie na inne. Naturalną odpowiedzią jest oczywiście „Jaki rodzajSet
nie obsługujeadd
?” - cóż, jeśli takie zachowanie jest do zaakceptowaniaentrySet()
, to dlaczego nie jest do przyjęciathis
? To powiedziawszy, jestem już w większości przekonany, dlaczego nie jest to taki wspaniały pomysł, jak kiedyś myślałem, ale nadal uważam, że warto go dalszej debaty, choćby po to, aby wzbogacić moje własne zrozumienie tego, co składa się na dobry projekt API / OOP.Collection
przedłużeniuMap
?Chociaż otrzymałeś wiele odpowiedzi, które dość bezpośrednio pokrywają twoje pytanie, myślę, że warto byłoby nieco cofnąć się i spojrzeć na pytanie bardziej ogólnie. To znaczy, aby nie patrzeć konkretnie na to, jak biblioteka Java została napisana, i przyjrzeć się, dlaczego jest napisana w ten sposób.
Problem polega na tym, że dziedziczenie modeluje tylko jeden typ podobieństwa. Jeśli wybierzesz dwie rzeczy, które wydają się „podobne do kolekcji”, prawdopodobnie możesz wybrać 8 lub 10 rzeczy, które mają ze sobą wspólnego. Jeśli wybierzesz inną parę „kolekcjonerskich” rzeczy, będą one również mieć 8 lub 10 wspólnych elementów - ale nie będą to takie same 8 lub 10 rzeczy, co pierwsza para.
Jeśli spojrzeć na kilkunastu różnych „kolekcja-like” rzeczy, praktycznie każdy z nich będzie prawdopodobnie mieć coś jak 8 lub 10 cech wspólnych z co najmniej jednym drugim - ale jeśli spojrzeć na to, co wspólne w poprzek każdego jednego z nich praktycznie nic nie zostało.
Jest to sytuacja, w której dziedziczenie (zwłaszcza dziedziczenie pojedyncze) po prostu nie jest dobrym modelem. Nie ma wyraźnej linii podziału między tym, które z nich są naprawdę kolekcjami, a które nie - ale jeśli chcesz zdefiniować sensowną klasę kolekcji, jesteś skazany na pozostawienie niektórych z nich. Jeśli zostawisz tylko kilka z nich, twoja klasa Collection będzie w stanie zapewnić dość rzadki interfejs. Jeśli pominiesz więcej, będziesz w stanie zapewnić bogatszy interfejs.
Niektórzy przyjmują również opcję mówiącą po prostu: „ten typ kolekcji obsługuje operację X, ale nie wolno jej używać, poprzez wyprowadzenie z klasy bazowej, która definiuje X, ale próba użycia klasy pochodnej 'X kończy się niepowodzeniem (np. , zgłaszając wyjątek).
To wciąż pozostawia jeden problem: prawie niezależnie od tego, które z nich pominiesz, a które włożysz, będziesz musiał wyznaczyć sztywną granicę między tym, jakie zajęcia są, a co nie. Bez względu na to, gdzie narysujesz tę linię, pozostanie ci jasny, raczej sztuczny podział na niektóre rzeczy, które są dość podobne.
źródło
Myślę, że powód jest subiektywny.
W C # myślę, że
Dictionary
rozszerza lub przynajmniej implementuje kolekcję:Również w Pharo Smalltak:
Ale istnieje asymetria w przypadku niektórych metod. Na przykład
collect:
will przyjmuje asocjację (odpowiednik wpisu), podczas gdydo:
przyjmuje wartości. Zapewniają inną metodękeysAndValuesDo:
iteracji słownika według pozycji.Add:
przyjmuje skojarzenie, aleremove:
zostało „zablokowane”:Jest to więc ostatecznie wykonalne, ale prowadzi do innych problemów dotyczących hierarchii klas.
To, co jest lepsze, jest subiektywne.
źródło
Odpowiedź cletus jest dobra, ale chcę dodać podejście semantyczne. Łączenie obu nie ma sensu, pomyśl o przypadku, gdy dodajesz parę klucz-wartość za pośrednictwem interfejsu kolekcji, a klucz już istnieje. Interfejs Map dopuszcza tylko jedną wartość skojarzoną z kluczem. Ale jeśli automatycznie usuniesz istniejący wpis z tym samym kluczem, po dodaniu kolekcja będzie miała taki sam rozmiar jak poprzednio - bardzo nieoczekiwane dla kolekcji.
źródło
Set
działa, aSet
nie realizowaćCollection
.Kolekcje Java są uszkodzone. Brakuje interfejsu Relation. Stąd Mapa rozszerza Relation rozszerza Set. Relacje (nazywane również multi-mapami) mają unikalne pary nazwa-wartość. Mapy (zwane też „funkcjami”) mają unikalne nazwy (lub klucze), które oczywiście odwzorowują wartości. Sekwencje rozszerzają mapy (gdzie każdy klucz jest liczbą całkowitą> 0). Torby (lub zestawy wielokrotne) rozszerzają mapy (gdzie każdy klucz jest elementem, a każda wartość to liczba razy, gdy element pojawia się w torbie).
Taka struktura umożliwiłaby przecięcie, połączenie itp. Szeregu „kolekcji”. Stąd hierarchia powinna wyglądać następująco:
Sun / Oracle / Java ppl - pobierz to dobrze następnym razem. Dzięki.
źródło
Jeśli spojrzysz na odpowiednią strukturę danych, możesz łatwo zgadnąć, dlaczego
Map
nie jest ona częściąCollection
. KażdyCollection
przechowuje pojedynczą wartość, gdzie jakoMap
przechowuje parę klucz-wartość. Zatem metody wCollection
interfejsie są niekompatybilne zMap
interfejsem. Na przykład wCollection
mamyadd(Object o)
. Jaka byłaby taka implementacja wMap
. Nie ma sensu mieć takiej metodyMap
. Zamiast tego mamyput(key,value)
metodę wMap
.Ten sam argument odnosi się do
addAll()
,remove()
iremoveAll()
metod. Więc głównym powodem jest różnica w sposobie przechowywania danych wMap
iCollection
. Również jeśli pamiętaszCollection
interfejs zaimplementowany wIterable
interfejsie, tj. Każdy interfejs z.iterator()
metodą powinien zwracać iterator, który musi pozwolić nam na iterację wartości przechowywanych wCollection
. Co teraz taka metoda zwróciMap
? Iterator klucza czy Iterator wartości? To też nie ma sensu.Istnieją sposoby na iterację kluczy i wartości przechowywanych w a
Map
i tak jest to częśćCollection
frameworka.źródło
There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.
Czy możesz pokazać przykładowy kod?add(Object o)
polegałaby na dodaniuEntry<ClassA, ClassB>
obiektu. AMap
można uznać zaCollection of Tuples
Właściwie, gdyby tak było
implements Map<K,V>, Set<Map.Entry<K,V>>
, to raczej się zgadzam. Wydaje się to nawet naturalne. Ale to nie działa zbyt dobrze, prawda? Powiedzmy, że mamyHashMap implements Map<K,V>, Set<Map.Entry<K,V>
,LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>
itd ... to wszystko dobrze, ale gdybyś to zrobiłentrySet()
, nikt nie zapomni o wdrożeniu tej metody i możesz być pewien, że możesz uzyskać entrySet dla dowolnej mapy, podczas gdy nie jesteś, jeśli masz nadzieję że osoba wdrażająca zaimplementowała oba interfejsy ...Powód, dla którego nie chcę,
interface Map<K,V> extends Set<Map.Entry<K,V>>
jest po prostu taki, że będzie więcej metod. W końcu to różne rzeczy, prawda? Również bardzo praktycznie, jeśli trafięmap.
w IDE, nie chcę tego widzieć.remove(Object obj)
, a.remove(Map.Entry<K,V> entry)
ponieważ nie mogę tego zrobićhit ctrl+space, r, return
i skończyć z tym.źródło
Map<K,V>
nie powinien przedłużać się,Set<Map.Entry<K,V>>
ponieważ:Map.Entry
znaków tym samym kluczem do tego samegoMap
, aleMap.Entry
s z tym samym kluczem do tego samegoSet<Map.Entry>
.źródło
Prosto i prosto. Kolekcja to interfejs, który oczekuje tylko jednego obiektu, podczas gdy mapa wymaga dwóch.
źródło