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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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

Редактировать ответ на комментарий:

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

Часто то, что вы будете делать, это создать объект и заполнить его с использованием его определенного типа (HashMap), каким-то способом "создать" или "инициализировать", но этот метод вернет "карту", ​​которая не нужно больше манипулировать как HashMap.

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

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

Ответ 4

enter image description here

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

  • HashMap Map m = new HashMap();

  • LinkedHashMap Map m = new LinkedHashMap();

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

  • WeakHashMap Map m = new WeakHashMap();

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

public void HashMap getMap(){
   return map;
}

Предположим, что требования к проекту меняются каждый раз следующим образом:

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

...

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

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

Ответ 9

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

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

Посмотрите исходный код:

В Map мы имеем метод containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue(значение объекта)

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

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

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

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

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

valueThrows:

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

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

Он требует, чтобы его реализации выполняли его, но "как" находится на его свободе, только чтобы гарантировать, что он вернется правильно.

В HashMap:

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

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

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