В чем разница между объектами HashMap и Map в Java? - PullRequest
323 голосов
/ 28 августа 2009

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

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

Ответы [ 13 ]

393 голосов
/ 28 августа 2009

Нет разницы между объектами; у вас есть 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, которые он разделяет (через методы доступа) с подклассами. Скажем, я пишу это с HashMap s, чтобы начать, потому что я думаю, что это подходящая структура, чтобы использовать при написании класса.

Позже Мэри пишет код, разделяющий его. У нее есть кое-что, что ей нужно сделать как с 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 раньше говорил, что он предоставляет HashMap s, но теперь он предоставляет 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 не изменился бы повлияли на ее код.

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

54 голосов
/ 28 августа 2009

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

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

20 голосов
/ 16 февраля 2015

enter image description here

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

  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 голосов
/ 28 августа 2009

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

ах, так что разница в том, что в Вообще, у карты есть определенные методы связано с этим. но есть разные способы или создание карты, такие как 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 реализует интерфейс очереди, а ArrayList - нет.

12 голосов
/ 28 августа 2009

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

8 голосов
/ 28 августа 2009

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

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

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

8 голосов
/ 28 августа 2009

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

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

3 голосов
/ 28 августа 2009

Вы создаете одинаковые карты.

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

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

...

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

foo (m1);
foo ((HashMap<String, Object>)m2); 
2 голосов
/ 28 сентября 2017

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

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)). Эта операция, вероятно, потребует линейного времени в размере карты для большинства реализаций интерфейса карты.

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

значение, присутствие которого на этой карте необходимо проверить

Возвращает: правда

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

valueThrows:

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

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

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

В HashMap:

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

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

2 голосов
/ 09 мая 2017

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...