Яка різниця між об'єктами HashMap та Map на Java?


349

Яка різниця між такими картами, які я створюю (в іншому питанні, люди відповідали, використовуючи їх, здавалося б, взаємозамінно, і мені цікаво, чи / як вони відрізняються):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();

Припустимо, ви реалізуєте за допомогою HashMap, а Mary використовує Map. Чи складе?
Гілберт

Відповіді:


446

Різниці між об’єктами немає; у вас є HashMap<String, Object>в обох випадках. Існує різниця в інтерфейсі, який ви маєте до об'єкта. У першому випадку інтерфейс є HashMap<String, Object>, тоді як у другому - це Map<String, Object>. Але базовий об’єкт той самий.

Перевага використання Map<String, Object>полягає в тому, що ви можете змінити базовий об'єкт на інший вид карти, не порушуючи контракт з будь-яким кодом, який ним використовується. Якщо ви визнаєте це таким HashMap<String, Object>, вам доведеться змінити договір, якщо ви хочете змінити базову реалізацію.


Приклад: Скажімо, я пишу цей клас:

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...
}

Клас має пару внутрішніх карт об’єкта string->, яким він ділиться (за допомогою методу accessor) з підкласами. Скажімо, я пишу це з HashMaps для початку, тому що я думаю, що це відповідна структура, яку слід використовувати при написанні класу.

Пізніше Мері пише код, підкласуючи його. У неї є щось, що їй потрібно зробити з обома, thingsі moreThings, природно, вона вважає, що це звичайний метод, і вона використовує той самий тип, який я використовував getThings/ getMoreThingsпри визначенні свого методу:

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

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

    // ...more...
}

Пізніше, я вирішив , що на насправді, це краще , якщо я використовую TreeMapзамість HashMapв Foo. Я оновлюю Foo, змінюючи HashMapна TreeMap. Тепер, SpecialFooбільше не компілюється, тому що я порушив контракт: Fooраніше говорив, що це передбачено HashMaps, але тепер він надає TreeMapsнатомість Тому нам доведеться виправити SpecialFooзараз (і така річ може прошиватись через кодову базу).

Якщо у мене не було дійсно вагомих причин поділитися тим, що моя реалізація використовує HashMap(і це трапляється), те, що я повинен був зробити, це оголосити getThingsі getMoreThingsяк тільки повернутися, Map<String, Object>не будучи більш конкретним, ніж це. Насправді, заборона вагомої причини зробити щось інше, навіть усередині, Fooмабуть, я повинен заявити thingsі moreThingsяк Map, ні 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...
}

Зауважте, як я зараз використовую Map<String, Object>всюди, де я можу, лише буду конкретним, коли створюю фактичні об'єкти.

Якби я зробив це, то Марія зробила б це:

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

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

... а зміна Fooне SpecialFooзупинила б збирання.

Інтерфейси (та базові класи) дозволяють нам розкрити лише стільки, скільки необхідно , зберігаючи нашу гнучкість під обкладинкою, щоб внести зміни в міру необхідності. Загалом, ми хочемо, щоб наші посилання були максимально базовими. Якщо нам не потрібно знати, що це HashMap, просто назвіть його Map.

Це не сліпе правило, але загалом кодування до самого загального інтерфейсу буде менш крихким, ніж кодування до чогось більш конкретного. Якби я пам’ятав це, я б не створив цю програму, Fooяку Мері створила на провал SpecialFoo. Якби Мері запам'ятала це, то, хоч я і заплутався Foo, вона заявила б про свій приватний метод Mapзамість цього, HashMapі мій Fooконтракт на зміну не вплинув би на її код.

Іноді ти не можеш цього зробити, іноді ти маєш бути конкретним. Але якщо у вас немає причин, помиліться на найменш специфічному інтерфейсі.


56

Map - це інтерфейс, який реалізує HashMap . Різниця полягає в тому, що у другій реалізації ваше посилання на HashMap дозволить використовувати лише функції, визначені в інтерфейсі Map, тоді як перша дозволить використовувати будь-які загальнодоступні функції в HashMap (що включає інтерфейс Map).

Напевно, це матиме більше сенсу, якщо ви прочитаєте навчальний посібник із інтерфейсом Sun


Я припускаю: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld

Це схоже на те, як часто список реалізовується як ArrayList
Джерард

26

введіть тут опис зображення

Карта має такі реалізації:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Карта дерева Map m = new TreeMap();

  4. Слабка карта Map m = new WeakHashMap();

Припустимо, ви створили один метод (це просто псевдокод).

public void HashMap getMap(){
   return map;
}

Припустимо, зміни ваших вимог проекту:

  1. Метод повинен повернути вміст карти - Необхідно повернутися HashMap.
  2. Метод повинен повернути ключі карти у порядку вставки - Необхідно змінити тип повернення HashMapна LinkedHashMap.
  3. Метод повинен повернути ключі карти в упорядкованому порядку - Необхідно змінити тип повернення LinkedHashMapна TreeMap.

Якщо ваш метод повертає конкретні класи замість того, що реалізує Mapінтерфейс, вам доведеться getMap()щоразу змінювати тип повернення методу.

Але якщо ви використовуєте функцію поліморфізму Java, а замість повернення конкретних класів використовуйте інтерфейс Map, це покращує повторне використання коду та зменшує вплив змін вимог.


17

Я просто збирався це зробити як коментар до прийнятої відповіді, але це стало занадто прикольно (ненавиджу відсутність перерв рядків)

ах, тому різниця полягає в тому, що в цілому карта має певні методи, пов'язані з нею. але існують різні способи або створення карти, наприклад, HashMap, і ці різні способи забезпечують унікальні методи, якими не всі карти є.

Саме так - і ви завжди хочете використовувати найзагальніший інтерфейс, який, можливо, можете. Розглянемо ArrayList vs LinkedList. Величезна різниця в тому, як ви їх використовуєте, але якщо ви використовуєте "Список", ви можете легко перемикатися між ними.

Насправді ви можете замінити праву частину ініціалізатора на більш динамічну операцію. як щодо щось подібне:

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

Таким чином, якщо ви збираєтеся заповнити колекцію сортуванням вставки, ви б використали зв'язаний список (сортування вставки у список масивів є злочинним.) Але якщо вам не потрібно буде сортувати його та просто додавати, ви використовуєте ArrayList (ефективніше для інших операцій).

Тут досить великий розтяг, оскільки колекції - не найкращий приклад, але в OO-дизайні однією з найважливіших концепцій є використання фасаду інтерфейсу для доступу до різних об'єктів з точно таким же кодом.

Редагувати у відповідь на коментар:

Що стосується коментаря до вашої карти нижче, так, використовуючи інтерфейс "Карта", ви обмежуєте лише ті методи, якщо ви не повертаєте колекцію назад із Map до HashMap (що ПОЛІТНО перемагає мету).

Часто, що ви будете робити, це створити об'єкт і заповнити його, використовуючи його конкретний тип (HashMap), якимсь методом "створити" або "ініціалізувати", але цей метод поверне "Карта", яка не повинна бути більше не маніпулюють як HashMap.

Якщо вам коли-небудь доводиться робити трансляцію, ви, ймовірно, використовуєте неправильний інтерфейс або ваш код недостатньо структурований. Зауважте, що один розділ вашого коду прийнятно трактувати як "HashMap", а інший трактує його як "Карта", але це має витікати "вниз". так що ти ніколи не кастиш.

Також зауважте напівтонний аспект ролей, позначений інтерфейсами. LinkedList робить хороший стек або чергу, ArrayList робить хороший стек, але жахлива черга (знову ж таки, видалення спричинить зміщення всього списку), тому LinkedList реалізує інтерфейс черги, ArrayList - ні.


але в цьому прикладі я отримую лише методи із загального класу List, правда? незалежно від того, я роблю це LinkedList () або ArrayList ()? це просто те, що якщо я використовую сортування вставки (який, я думаю, повинен бути методом для списку, який LinkedList і ArrayList отримують за спадщиною), це працює набагато швидше на LinkedList?
Тоні Старк

Я здогадуюсь, що я шукаю - це чи ні, коли я скажу Map <string, string> m = new HashMap <string, string> () моя Map m може використовувати методи, характерні для HashMap, чи ні. Я думаю, що це не може?
Тоні Старк

ах, зачекайте, ні, моя карта m зверху повинна мати методи з HashMap.
Тоні Старк

тому в основному єдиною перевагою використання Map в "інтерфейсі" є те, що якщо у мене є метод, який вимагає карту, я гарантую, що будь-який тип карти буде працювати в цьому методі. але якщо я використовував хешмап, я кажу, що метод працює лише з хешмапами. або, кажучи іншим способом, мій метод використовує лише методи, визначені в класі Map, але успадковані іншими класами, які розширюють Map.
Тоні Старк

на додаток до привілей, про які ви згадали вище, коли використання List означає, що мені не потрібно вирішувати, який тип списку я хочу до виконання, тоді як якщо інтерфейс не існував, я повинен вибрати його перед компіляцією та запуском
Тоні Старк

12

Як зазначають TJ Crowder та Adamski, одна посилання - на інтерфейс, інша - на конкретну реалізацію інтерфейсу. За словами Джошуа Блока, ви завжди повинні намагатися кодувати інтерфейси, щоб краще обробляти зміни основної реалізації - тобто, якщо HashMap раптом не був ідеальним для вашого рішення, і вам потрібно було змінити реалізацію карти, ви все одно можете використовувати Map інтерфейсу та змінити тип інстанції.


8

У вашому другому прикладі посилання "map" має тип Map, який є інтерфейсом, реалізованим HashMap(та іншими типами Map). Цей інтерфейс є договором, який говорить про те, що об'єкт відображає ключі до значень та підтримує різні операції (наприклад put, get). Він не говорить нічого про реалізацію з Map(в даному випадку HashMap).

Другий підхід, як правило, є кращим, оскільки ти зазвичай не хочеш піддавати конкретну реалізацію карти методам, що використовують Mapабо за допомогою визначення API.


8

Карта - це статичний тип карти, тоді як HashMap - динамічний тип карти. Це означає, що компілятор буде розглядати ваш об’єкт карт як один з типів Map, хоча під час виконання він може вказувати на будь-який його підтип.

Ця практика програмування на інтерфейсах замість реалізацій має додаткову перевагу, що залишається гнучким: Ви можете, наприклад, замінити динамічний тип карти під час виконання, якщо він є підтипом Map (наприклад, LinkedHashMap), і змінити поведінку карти на політ.

Хорошим правилом є збереження якнайбільше абстрактних на рівні API: Якщо, наприклад, метод, який ви програмуєте, повинен працювати над картами, тоді достатньо оголосити параметр як Map, а не строгіший (оскільки менш абстрактний) тип HashMap . Таким чином, споживач вашого API може бути гнучким щодо того, яку реалізацію Карти вони хочуть передати вашому методу.


4

Додавши до голосової відповіді та ще багатьох із них вище, наголошуючи на "більш загальному, кращому", я хотів би ще трохи копати.

Map- це структура договору, в той час HashMapяк це реалізація, що забезпечує власні методи вирішення різних реальних проблем: як обчислити індекс, яка ємність і як її збільшити, як вставити, як зберегти індекс унікальним тощо.

Давайте розглянемо вихідний код:

У Mapнас є метод containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (значення об'єкта)

Повертає істину, якщо ця карта відображає одну або кілька клавіш із заданим значенням. Більш формально, повертає істину тоді і тільки тоді, коли ця карта містить принаймні одне відображення до такого значення v, як (value==null ? v==null : value.equals(v)). Ця операція, ймовірно, потребуватиме лінійного часу у розмірі карти для більшості реалізацій інтерфейсу Map.

Параметри: значення

значення, наявність якого на цій карті підлягає випробуванню

Повертає: правда

якщо ця карта відображає один або кілька ключів до зазначених

valueThrow:

ClassCastException - якщо значення невідповідного типу для цієї карти (необов’язково)

NullPointerException - якщо вказане значення є нульовим і ця карта не дозволяє нульових значень (необов'язково)

Для його впровадження потрібні її реалізація, але "як це зробити" є на її свободі, лише для того, щоб вона поверталася правильною.

В HashMap:

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

Виявляється, HashMapдля тестування використовується хеш-код, чи ця карта містить ключ. Таким чином, він має перевагу алгоритму хешу.


3

Ви створюєте ті ж карти.

Але ви можете заповнити різницю, коли будете її використовувати. У першому випадку ви зможете використовувати спеціальні методи HashMap (але я не пам'ятаю, щоб хтось був дуже корисним), і ви зможете передавати це як параметр HashMap:

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

...

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

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

3

Map - інтерфейс, а Hashmap - клас, який реалізує Map Interface


1

Map - це інтерфейс, а Hashmap - клас, який реалізує це.

Тож у цій реалізації ви створюєте ті самі об’єкти


0

HashMap - це реалізація Map, тому вона зовсім однакова, але має метод "clone ()", як я бачу в довідковому посібнику))


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

Перш за все Mapце інтерфейс має різні реалізації , як - HashMap, TreeHashMap, і LinkedHashMapт.д. Інтерфейс працює як суперклас для реалізує класу. Отже, згідно з правилом ООП, будь-який конкретний клас, який реалізується Map, Mapтакож є. Це означає, що ми можемо призначити / поставити будь-яку HashMapзмінну типу аMap змінної типу без будь-якого типу кастингу.

У цьому випадку ми можемо призначити , map1щоб map2без будь - яких лиття або будь-якого програшу даних -

map2 = map1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.