В чем разница между объектами HashMap и Map в Java?

350

В чем разница между следующими картами, которые я создаю (в другом вопросе люди ответили, используя их, казалось бы, взаимозаменяемо, и мне интересно, если / как они отличаются):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Тони Старк
источник
Предположим, вы реализуете с использованием HashMap, а Мэри использует 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-> object, которые он разделяет (через методы доступа) с подклассами. Допустим, я пишу это с 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контракт на изменение не повлиял бы на ее код.

Иногда ты не можешь этого сделать, иногда ты должен быть конкретным. Но если у вас нет причин быть ошибочными в сторону наименее конкретного интерфейса.

TJ Crowder
источник
56

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

Это, вероятно, будет иметь больше смысла, если вы прочитаете руководство по интерфейсу Sun.

Графика Нуб
источник
Я предполагаю: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld
Это похоже на то, как часто список реализуется как ArrayList
Джерард
27

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

Карта имеет следующие реализации:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Древовидная карта Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Предположим, вы создали один метод (это просто псевдокод).

public void HashMap getMap(){
   return map;
}

Предположим, что требования вашего проекта изменились:

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

Если ваш метод возвращает конкретные классы вместо чего-то, что реализует Mapинтерфейс, вы должны getMap()каждый раз менять тип возвращаемого метода.

Но если вы используете функцию полиморфизма Java и вместо того, чтобы возвращать определенные классы, используете интерфейс Map, это улучшает возможность повторного использования кода и уменьшает влияние изменений требований.

Атиш Шимпи
источник
17

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

ах, так что разница в том, что в общем случае с Map связаны определенные методы. но существуют разные способы создания карты, например HashMap, и эти разные способы предоставляют уникальные методы, которые есть не на всех картах.

Точно - и вы всегда хотите использовать самый общий интерфейс, какой только можете. Рассмотрим ArrayList против LinkedList. Огромная разница в том, как вы их используете, но если вы используете «Список», вы можете легко переключаться между ними.

Фактически, вы можете заменить правую часть инициализатора более динамичным оператором. как насчет чего-то вроде этого:

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

Таким образом, если вы собираетесь заполнить коллекцию сортировкой вставок, вы бы использовали связанный список (сортировка вставок в список массивов является преступной). Но если вам не нужно сохранять сортировку и просто добавляете, вы используете ArrayList (более эффективный для других операций).

Это довольно большая проблема, потому что коллекции - не лучший пример, но в ОО-дизайне одной из наиболее важных концепций является использование фасада интерфейса для доступа к различным объектам с одинаковым кодом.

Изменить ответ на комментарий:

Что касается вашего комментария к карте, приведенного ниже, то при использовании интерфейса «Карта» «Да» вы можете использовать только эти методы, если только вы не приведете коллекцию обратно из карты в HashMap (что ПОЛНОСТЬЮ не соответствует цели).

Часто вы будете создавать объект и заполнять его, используя определенный тип (HashMap), в каком-то методе «create» или «initialize», но этот метод будет возвращать «Map», который не нужно манипулировать как HashMap больше.

Кстати, если вам когда-либо придется выполнять приведение типов, возможно, вы используете неправильный интерфейс или ваш код недостаточно структурирован. Обратите внимание, что допустимо, чтобы один раздел вашего кода рассматривал его как «HashMap», в то время как другой обрабатывает его как «Map», но это должно течь «вниз». так что вы никогда не будете кастом.

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

Билл К
источник
но в этом примере я получаю только методы из общего класса List, верно? независимо от того, сделаю ли я это LinkedList () или ArrayList ()? просто если я использую сортировку вставкой (которая, я полагаю, должна быть методом для List, который LinkedList и ArrayList получают путем наследования), это работает намного быстрее в LinkedList?
Тони Старк
я думаю, что я ищу, является ли или нет, когда я говорю Map <string, string> m = new HashMap <string, string> (), моя карта m может использовать методы, специфичные для HashMap, или нет. Я думаю, что это не может?
Тони Старк
ах, подожди, нет, моя карта m сверху должна иметь методы из HashMap.
Тони Старк
так что, по сути, единственное преимущество использования Map в «смысле интерфейса» заключается в том, что если у меня есть метод, который требует карту, я гарантирую, что любой тип карты будет работать в этом методе. но если я использовал hashmap, я говорю, что метод работает только с hashmaps. или, другими словами, мой метод использует только методы, определенные в классе Map, но унаследованные другими классами, которые расширяют Map.
Тони Старк
в дополнение к перку, который вы упомянули выше, где использование List означает, что мне не нужно решать, какой тип List я хочу до времени выполнения, в то время как если бы интерфейса не существовало, я бы выбрал один до компиляции и запуска
Тони Старк
12

Как отметили TJ Crowder и Adamski, одна ссылка относится к интерфейсу, другая - к конкретной реализации интерфейса. По словам Джошуа Блока, вы всегда должны пытаться кодировать интерфейсы, чтобы позволить вам лучше обрабатывать изменения в базовой реализации - т. Е. Если HashMap внезапно оказался не идеальным для вашего решения и вам нужно было изменить реализацию карты, вы все равно можете использовать карту интерфейс, и измените тип экземпляра.

aperkins
источник
8

Во втором примере ссылка «карта» имеет тип Map, который является интерфейсом, реализованным HashMap(и другими типами Map). Этот интерфейс является контрактом, в котором говорится, что объект сопоставляет ключи со значениями и поддерживает различные операции (например put, get). Он не говорит ничего о реализации из Map(в данном случае HashMap).

Второй подход, как правило, предпочтительнее, так как обычно вы не хотите раскрывать конкретную реализацию карты методам, использующим Mapили через определение API.

Адамский
источник
8

Карта - это статический тип карты, а HashMap - динамический тип карты. Это означает, что компилятор будет обрабатывать ваш объект карты как объект типа Map, хотя во время выполнения он может указывать на любой его подтип.

Эта практика программирования на основе интерфейсов вместо реализаций имеет дополнительное преимущество, заключающееся в том, что она остается гибкой: например, вы можете заменить динамический тип карты во время выполнения, если это подтип карты (например, LinkedHashMap), и изменить поведение карты в муха.

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

Матиас
источник
4

Добавляя к наиболее популярному ответу и многим из вышеперечисленных подчеркивая «более общий, лучший», я хотел бы еще немного покопаться.

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

Давайте посмотрим на исходный код:

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

boolean containsKey(Object key);

JavaDoc:

логический java.util.Map.containsValue (значение объекта)

Возвращает true, если эта карта отображает один или несколько ключей на указанное значение. Более формально, возвращает true тогда и только тогда, когда эта карта содержит хотя бы одно сопоставление с таким значением v, что (value==null ? v==null : value.equals(v)). Эта операция, вероятно, потребует линейного времени в размере карты для большинства реализаций интерфейса карты.

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

значение, чье присутствие на этой карте нужно проверить

Возвращает: true

если эта карта отображает один или несколько ключей на указанный

valueThrows:

ClassCastException - если значение имеет неподходящий тип для этой карты (необязательно)

NullPointerException - если указанное значение является нулем, и эта карта не допускает нулевые значения (необязательно)

Для его реализации требуются свои реализации, но «как это сделать» находится на его свободе, только для того, чтобы гарантировать, что он вернётся правильно.

В HashMap:

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

Оказывается, что HashMapиспользует хеш-код для проверки, если эта карта содержит ключ. Таким образом, он имеет преимущество алгоритма хеширования.

WesternGun
источник
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

Карта - это интерфейс, а Hashmap - это класс, реализующий интерфейс карты.


источник
1

Map - это интерфейс, а Hashmap - это класс, который реализует это.

Таким образом, в этой реализации вы создаете те же объекты

Диего Диас
источник
0

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

kolyaseg
источник
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
Razib
источник