Jaka jest różnica między obiektami HashMap i Map w Javie?


349

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>();

Załóżmy, że implementujesz za pomocą HashMap, a Mary używa Map. Czy to się skompiluje?
GilbertS

Odpowiedzi:


446

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 jest HashMap<String, Object>, podczas gdy w drugim jest Map<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 jako HashMap<String, Object>, musisz zmienić umowę, jeśli chcesz zmienić implementację.


Przykład: Załóżmy, że piszę tę klasę:

class Foo {
    private HashMap<String, Object> things;
    private HashMap<String, Object> moreThings;

    protected HashMap<String, Object> getThings() {
        return this.things;
    }

    protected HashMap<String, Object> getMoreThings() {
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

Klasa ma kilka wewnętrznych map ciągu typu string>, które dzieli (za pomocą metod akcesorów) z podklasami. Powiedzmy, że piszę od HashMappoczą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 thingsrzeczami moreThings, więc naturalnie umieszcza to we wspólnej metodzie i używa tego samego typu, którego użyłam na getThings/ getMoreThingspodczas definiowania swojej metody:

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

Później zdecydować, że właściwie, to lepiej, jeśli mogę użyć TreeMapzamiast HashMapw Foo. Aktualizuję Foo, zmieniam HashMapna TreeMap. Teraz SpecialFoojuż się nie kompiluje, bo złamałem kontrakt: Foozwykłem mawiać, że to HashMaps, ale teraz to zapewnia TreeMaps. Więc musimy to SpecialFooteraz 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ć getThingsi getMoreThingspo 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 środku Fooprawdopodobnie powinienem zadeklarować thingsi moreThingsjako Map, nie HashMap/ TreeMap:

class Foo {
    private Map<String, Object> things;             // <== Changed
    private Map<String, Object> moreThings;         // <== Changed

    protected Map<String, Object> getThings() {     // <== Changed
        return this.things;
    }

    protected Map<String, Object> getMoreThings() { // <== Changed
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

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:

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

... a zmiana Foonie SpecialFoozatrzymał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 to Map.

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 Footakiego zestawu, w którym Mary byłaby porażka SpecialFoo. Gdyby Mary to zapamiętała, to mimo że popełniłem błąd Foo, zadeklarowałaby swoją prywatną metodę Mapzamiast, HashMapa moja zmiana Fooumowy 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.


56

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


Zakładam: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld,

Jest podobny do tego, jak często List jest implementowany jako ArrayList
Gerard

26

wprowadź opis zdjęcia tutaj

Mapa ma następujące implementacje:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Mapa drzewa Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Załóżmy, że utworzyłeś jedną metodę (to tylko pseudokod).

public void HashMap getMap(){
   return map;
}

Załóżmy, że zmieniają się wymagania twojego projektu:

  1. Metoda powinna zwrócić zawartość mapy - Konieczność zwrotu HashMap.
  2. Metoda powinna zwrócić klucze mapy w kolejności wstawiania - Należy zmienić typ zwrotu HashMapna LinkedHashMap.
  3. Metoda powinna zwrócić klucze mapy w posortowanej kolejności - Konieczna jest zmiana typu zwrotu LinkedHashMapna TreeMap.

Jeśli twoja metoda zwraca określone klasy zamiast czegoś, co implementuje Mapinterfejs, musisz za getMap()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ń.


17

Chciałem to zrobić jako komentarz do zaakceptowanej odpowiedzi, ale stało się to zbyt funky (nie znoszę łamania linii)

ah, więc różnica polega na tym, że mapa ma z sobą pewne metody. ale istnieją różne sposoby tworzenia mapy, takie jak HashMap, i te różne sposoby zapewniają unikalne metody, których nie mają wszystkie mapy.

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:

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

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.


ale w tym przykładzie otrzymuję metody tylko z ogólnej klasy List, prawda? niezależnie od tego, czy zmienię go na LinkedList () czy ArrayList ()? po prostu jeśli użyję sortowania wstawiania (które, jak sądzę, musi być metodą dla List, które LinkedList i ArrayList uzyskują przez dziedziczenie), działa znacznie szybciej na LinkedList?
Tony Stark

Chyba szukam tego, czy kiedy mówię Map <ciąg, ciąg> m = nowy HashMap <ciąg, ciąg> () moja Mapa m może używać metod specyficznych dla HashMap, czy nie. Myślę, że nie może?
Tony Stark,

ah, czekaj, nie, moja mapa m z góry musi mieć metody z HashMap.
Tony Stark,

więc w zasadzie jedynym atutem korzystania z mapy w sensie interfejsu jest to, że jeśli mam metodę, która wymaga mapy, gwarantuję, że każda metoda mapy będzie działać w tej metodzie. ale jeśli użyłem mapowania, to mówię, że metoda działa tylko z mapami skrótów. lub, inaczej mówiąc, moja metoda używa tylko metod zdefiniowanych w klasie Map, ale odziedziczonych przez inne klasy rozszerzające Mapę.
Tony Stark

oprócz profitu, o którym wspomniałeś powyżej, użycie Listu oznacza, że ​​nie muszę decydować, jakiego rodzaju Listy chcę do czasu uruchomienia, natomiast jeśli interfejs nie istniał, musiałbym wybrać jedną przed kompilacją i uruchomieniem
Tony Stark

12

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.


8

W drugim przykładzie odniesienie „map” jest typu Map, czyli interfejs implementowany przez HashMap(i inne typy Map). Ten interfejs jest umowa mówi, że obiekt odwzorowuje klucze do wartości i wspiera różne operacje (np put, get). To mówi nic o realizacji z Map(w tym przypadku HashMap).

Drugie podejście jest ogólnie preferowane, ponieważ zazwyczaj nie chcesz narażać konkretnej implementacji mapy na metody wykorzystujące Mapdefinicję API lub za jej pośrednictwem.


8

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.


4

Dodając do najlepiej głosowanej odpowiedzi i wielu powyżej podkreślających „bardziej ogólny, lepszy”, chciałbym kopać trochę więcej.

Mapjest umową o strukturze, podczas gdy HashMapjest 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 Mapmamy metodę containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (wartość obiektu)

Zwraca wartość true, jeśli mapa odwzorowuje jeden lub więcej kluczy na określoną wartość. Bardziej formalnie, zwraca true wtedy i tylko wtedy ta mapa zawiera co najmniej jedno mapowanie do wartości vtakich, że (value==null ? v==null : value.equals(v)). Ta operacja będzie prawdopodobnie wymagać czasu liniowego w rozmiarze mapy dla większości implementacji interfejsu mapy.

Parametry: wartość

wartość, której obecność na tej mapie ma zostać przetestowana

Zwraca: prawda

jeśli ta mapa odwzorowuje jeden lub więcej kluczy na określony

valueThrows:

ClassCastException - jeśli wartość jest nieodpowiedniego typu dla tej mapy (opcjonalnie)

NullPointerException - jeśli określona wartość jest pusta, a ta mapa nie zezwala na wartości zerowe (opcjonalnie)

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:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

Okazuje się, że HashMapużywa skrótu do sprawdzenia, czy ta mapa zawiera klucz. Ma więc zaletę algorytmu mieszającego.


3

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:

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 

3

Mapa jest interfejsem, a Hashmap to klasa implementująca interfejs mapy


1

Mapa to interfejs, a Hashmap to klasa, która to implementuje.

W tej implementacji tworzysz te same obiekty


0

HashMap to implementacja Map, więc jest całkiem taka sama, ale ma metodę „klon ()”, jak widzę w podręczniku))


0
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

Przede wszystkim Mapto interfejs ma inną realizację podobnego - HashMap, TreeHashMap, LinkedHashMapitd. Interfejs działa jak super-klasy do klasy wykonawczego. Tak więc, zgodnie z zasadą OOP, każda konkretna klasa, która implementuje Mapjest Maprównież. Oznacza to, że możemy przypisać / umieścić dowolną HashMapzmienną typu do Mapzmiennej typu bez żadnego rodzaju rzutowania.

W tym przypadku możemy przypisać map1do map2bez przesyłania lub utraty danych -

map2 = map1
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.