Jaka jest różnica między następującymi mapami, które tworzę (w innym pytaniu, ludzie odpowiadali za ich pomocą pozornie zamiennie i zastanawiam się, czy / jak się różnią):
HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
java
dictionary
hashmap
Tony Stark
źródło
źródło
Odpowiedzi:
Nie ma różnicy między przedmiotami; masz
HashMap<String, Object>
w obu przypadkach. Istnieje różnica w interfejsie, jaki masz do obiektu. W pierwszym przypadku interfejs jestHashMap<String, Object>
, podczas gdy w drugim jestMap<String, Object>
. Ale podstawowy obiekt jest taki sam.Zaletą użycia
Map<String, Object>
jest to, że możesz zmienić obiekt bazowy na inny rodzaj mapy bez zerwania umowy z dowolnym kodem, który go używa. Jeśli zadeklarujesz to jakoHashMap<String, Object>
, musisz zmienić umowę, jeśli chcesz zmienić implementację.Przykład: Załóżmy, że piszę tę klasę:
Klasa ma kilka wewnętrznych map ciągu typu string>, które dzieli (za pomocą metod akcesorów) z podklasami. Powiedzmy, że piszę od
HashMap
początku, ponieważ uważam, że jest to odpowiednia struktura do użycia podczas pisania klasy.Później Mary pisze kod podklasujący go. Ma coś, co musi zrobić z obiema
things
rzeczamimoreThings
, więc naturalnie umieszcza to we wspólnej metodzie i używa tego samego typu, którego użyłam nagetThings
/getMoreThings
podczas definiowania swojej metody:Później zdecydować, że właściwie, to lepiej, jeśli mogę użyć
TreeMap
zamiastHashMap
wFoo
. AktualizujęFoo
, zmieniamHashMap
naTreeMap
. TerazSpecialFoo
już się nie kompiluje, bo złamałem kontrakt:Foo
zwykłem mawiać, że toHashMap
s, ale teraz to zapewniaTreeMaps
. Więc musimy toSpecialFoo
teraz naprawić (i tego rodzaju rzeczy mogą przeniknąć przez bazę kodu).O ile nie miałem naprawdę dobrego powodu do dzielenia się tym, że moja implementacja używała
HashMap
(i tak się dzieje), to co powinienem był zrobić, to zadeklarowaćgetThings
igetMoreThings
po prostu powrócić,Map<String, Object>
nie będąc bardziej szczegółowym. W rzeczywistości, z wyjątkiem dobrego powodu, aby zrobić coś innego, nawet w środkuFoo
prawdopodobnie powinienem zadeklarowaćthings
imoreThings
jakoMap
, nieHashMap
/TreeMap
:Zwróć uwagę, że teraz używam
Map<String, Object>
wszędzie, gdzie mogę, będąc konkretnym tylko podczas tworzenia rzeczywistych obiektów.Gdybym to zrobił, Mary zrobiłaby to:
... a zmiana
Foo
nieSpecialFoo
zatrzymałaby kompilacji.Interfejsy (i klasy podstawowe) pozwalają nam ujawniać tylko tyle, ile jest konieczne , zachowując naszą elastyczność pod przykryciem, aby wprowadzać odpowiednie zmiany. Ogólnie rzecz biorąc, chcemy, aby nasze referencje były jak najbardziej podstawowe. Jeśli nie musimy wiedzieć, że to jest
HashMap
, po prostu nazwij toMap
.Nie jest to reguła ślepa, ale ogólnie kodowanie do najbardziej ogólnego interfejsu będzie mniej kruche niż kodowanie do czegoś bardziej konkretnego. Gdybym to pamiętał, nie stworzyłbym
Foo
takiego zestawu, w którym Mary byłaby porażkaSpecialFoo
. Gdyby Mary to zapamiętała, to mimo że popełniłem błądFoo
, zadeklarowałaby swoją prywatną metodęMap
zamiast,HashMap
a moja zmianaFoo
umowy nie wpłynęłaby na jej kod.Czasami nie możesz tego zrobić, czasem musisz być konkretny. Ale jeśli nie masz ku temu powodu, popełniaj błąd w kierunku najmniej szczegółowego interfejsu.
źródło
Mapa to interfejs, który implementuje HashMap . Różnica polega na tym, że w drugiej implementacji odniesienie do HashMap pozwoli tylko na użycie funkcji zdefiniowanych w interfejsie Map, podczas gdy pierwsze pozwoli na użycie dowolnych funkcji publicznych w HashMap (która obejmuje interfejs Map).
Prawdopodobnie będzie to bardziej sensowne, jeśli przeczytasz samouczek interfejsu Sun
źródło
Mapa ma następujące implementacje:
HashMap
Map m = new HashMap();
LinkedHashMap
Map m = new LinkedHashMap();
Mapa drzewa
Map m = new TreeMap();
WeakHashMap
Map m = new WeakHashMap();
Załóżmy, że utworzyłeś jedną metodę (to tylko pseudokod).
Załóżmy, że zmieniają się wymagania twojego projektu:
HashMap
.HashMap
naLinkedHashMap
.LinkedHashMap
naTreeMap
.Jeśli twoja metoda zwraca określone klasy zamiast czegoś, co implementuje
Map
interfejs, musisz zagetMap()
każdym razem zmieniać typ zwracanej metody.Ale jeśli użyjesz funkcji polimorfizmu Java i zamiast zwracać określone klasy, skorzystaj z interfejsu
Map
, poprawi to możliwość ponownego użycia kodu i zmniejszy wpływ zmian wymagań.źródło
Chciałem to zrobić jako komentarz do zaakceptowanej odpowiedzi, ale stało się to zbyt funky (nie znoszę łamania linii)
Dokładnie - i zawsze chcesz używać najbardziej ogólnego interfejsu, jaki możesz. Rozważ ArrayList vs LinkedList. Ogromna różnica w sposobie korzystania z nich, ale jeśli używasz „Listy”, możesz łatwo przełączać się między nimi.
W rzeczywistości można zastąpić prawą stronę inicjatora bardziej dynamiczną instrukcją. co powiesz na coś takiego:
W ten sposób, jeśli zamierzasz wypełnić kolekcję sortowaniem wstawiania, użyłbyś połączonej listy (sortowanie wstawiania do listy tablic jest przestępstwem). Ale jeśli nie musisz utrzymywać sortowania i dodajesz, korzystasz z ArrayList (bardziej wydajny dla innych operacji).
Jest to tutaj dość duży odcinek, ponieważ kolekcje nie są najlepszym przykładem, ale w projekcie OO jedną z najważniejszych koncepcji jest użycie fasady interfejsu do uzyskania dostępu do różnych obiektów z dokładnie tym samym kodem.
Edytuj odpowiedź na komentarz:
Jeśli chodzi o komentarz do mapy poniżej, tak za pomocą interfejsu „Mapa” ogranicza się tylko do tych metod, chyba że ponownie przerzucisz kolekcję z mapy do HashMap (co CAŁKOWICIE pokonuje cel).
Często to, co zrobisz, to utworzenie obiektu i wypełnienie go za pomocą określonego typu (HashMap), w jakiejś metodzie „create” lub „initialize”, ale ta metoda zwróci „Mapę”, która nie musi być manipulowane jako HashMap.
Jeśli przy okazji musisz rzucić, prawdopodobnie używasz niewłaściwego interfejsu lub kod nie jest wystarczająco uporządkowany. Zauważ, że dopuszczalne jest, aby jedna sekcja twojego kodu traktowała go jako „HashMap”, podczas gdy druga traktuje go jako „Mapę”, ale powinno to płynąć „w dół”. abyś nigdy nie rzucał.
Zwróć też uwagę na częściowo schludny aspekt ról wskazywanych przez interfejsy. LinkedList tworzy dobry stos lub kolejkę, ArrayList tworzy dobry stos, ale przerażająca kolejka (ponownie, usunięcie spowodowałoby przesunięcie całej listy), więc LinkedList implementuje interfejs kolejki, ArrayList nie.
źródło
Jak zauważyli TJ Crowder i Adamski, jedno odniesienie dotyczy interfejsu, a drugie konkretnej implementacji interfejsu. Według Joshua Blocka, zawsze powinieneś próbować kodować interfejsy, aby umożliwić ci lepszą obsługę zmian w podstawowej implementacji - tj. Jeśli HashMap nagle nie był idealny dla twojego rozwiązania i musiałeś zmienić implementację mapy, nadal możesz użyć mapy interfejs i zmień typ wystąpienia.
źródło
W drugim przykładzie odniesienie „map” jest typu
Map
, czyli interfejs implementowany przezHashMap
(i inne typyMap
). Ten interfejs jest umowa mówi, że obiekt odwzorowuje klucze do wartości i wspiera różne operacje (npput
,get
). To mówi nic o realizacji zMap
(w tym przypadkuHashMap
).Drugie podejście jest ogólnie preferowane, ponieważ zazwyczaj nie chcesz narażać konkretnej implementacji mapy na metody wykorzystujące
Map
definicję API lub za jej pośrednictwem.źródło
Mapa jest statycznym typem mapy, a HashMap jest dynamicznym typem mapy. Oznacza to, że kompilator będzie traktował obiekt mapy jako obiekt typu Mapa, nawet jeśli w czasie wykonywania może wskazywać na dowolny jego podtyp.
Ta praktyka programowania w oparciu o interfejsy zamiast implementacji ma tę dodatkową zaletę, że pozostaje elastyczna: możesz na przykład zastąpić dynamiczny typ mapy w czasie wykonywania, o ile jest ona podtypem mapy (np. LinkedHashMap), i zmienić zachowanie mapy na Mucha.
Dobrą zasadą jest pozostanie jak najbardziej abstrakcyjnym na poziomie API: jeśli na przykład programowana metoda musi działać na mapach, wystarczy zadeklarować parametr jako Map zamiast bardziej rygorystycznego (ponieważ mniej abstrakcyjnego) typu HashMap . W ten sposób konsument interfejsu API może elastycznie decydować o tym, jaki rodzaj implementacji mapy chce przekazać do Twojej metody.
źródło
Dodając do najlepiej głosowanej odpowiedzi i wielu powyżej podkreślających „bardziej ogólny, lepszy”, chciałbym kopać trochę więcej.
Map
jest umową o strukturze, podczas gdyHashMap
jest implementacją zapewniającą własne metody radzenia sobie z różnymi rzeczywistymi problemami: jak obliczyć indeks, jaka jest pojemność i jak ją zwiększyć, jak wstawić, jak zachować indeks unikalny itp.Spójrzmy na kod źródłowy:
W
Map
mamy metodęcontainsKey(Object key)
:JavaDoc:
Wymaga to implementacji, aby go zaimplementować, ale „jak to zrobić” jest na wolności, tylko po to, aby upewnić się, że zwraca poprawnie.
W
HashMap
:Okazuje się, że
HashMap
używa skrótu do sprawdzenia, czy ta mapa zawiera klucz. Ma więc zaletę algorytmu mieszającego.źródło
Tworzysz te same mapy.
Ale możesz wypełnić różnicę, kiedy z niej skorzystasz. W pierwszym przypadku będziesz mógł użyć specjalnych metod HashMap (ale nie pamiętam, aby ktoś był naprawdę przydatny), i będziesz mógł przekazać je jako parametr HashMap:
źródło
Mapa jest interfejsem, a Hashmap to klasa implementująca interfejs mapy
źródło
Mapa to interfejs, a Hashmap to klasa, która to implementuje.
W tej implementacji tworzysz te same obiekty
źródło
HashMap to implementacja Map, więc jest całkiem taka sama, ale ma metodę „klon ()”, jak widzę w podręczniku))
źródło
Przede wszystkim
Map
to interfejs ma inną realizację podobnego -HashMap
,TreeHashMap
,LinkedHashMap
itd. Interfejs działa jak super-klasy do klasy wykonawczego. Tak więc, zgodnie z zasadą OOP, każda konkretna klasa, która implementujeMap
jestMap
również. Oznacza to, że możemy przypisać / umieścić dowolnąHashMap
zmienną typu doMap
zmiennej typu bez żadnego rodzaju rzutowania.W tym przypadku możemy przypisać
map1
domap2
bez przesyłania lub utraty danych -źródło