Java: отличия Map и HashMap

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Disclaimer

Всем привет! Я начинающий Java-разработчик. В рамках развития своей карьеры я решил сделать две вещи:

  • Завести канал в ТГ, где собираюсь рассказывать о себе, своем пути и проблемах, с которыми сталкиваюсь - https://t.me/java_wine

  • Завести блог на Хабре, куда буду выкладывать переводы материалов, которые использую для своего обучения и развития.

Надеюсь, буду полезен сообществу и новичкам, которые пойдут по моим или чьим-то еще стопам.

Run!


Введение

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

Назначение интерфейсов

Интерфейс - контракт, определяющий поведение класса. Каждый класс, реализующий интерфейс, должен исполнять этот контракт (реализовать все его методы). Чтобы лучше с этим разобраться, давайте представим себе автомобиль: Лада. Ниссан, Мерседес, Jeep или даже Бэтмобиль. Термин "автомобиль" подразумевает некоторые качества и поведение. Любой объект, обладающий этими качествами, можно назвать автомобилем. Поэтому, каждый из нас представляет автомобиль по-своему.

Интерфейсы устроены похожим образом. Map - это абстракция, которая определяет определенное поведение. Только класс, обладающий этим поведением может быть типа Map.

Различные реализации

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

Hashmap - наиболее простая реализация Map, которая обеспечивает базовую функциональность. Две другие реализации - TreeMap и LinkedHashMap - предоставляют дополнительные возможности.

Вот более подробная (но не полная) иерархия:

Программирование на уровне реализаций

Представьте, что нам нужно вывести в консоли ключи и значения Map:

public class HashMapPrinter {
    
    public void printMap(final HashMap<?, ?> map) {
        for (final Map.Entry<?, ?> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

Небольшой метод, который делает необходимую работу. Тем не менее, есть проблемка. Он будет работать только с объектом типа HashMap. Следовательно, каждый раз когда мы будем пытаться в него передать объект типа TreeMap или даже HashMap, на который ссылается переменная типа Map, будет возникать ошибка.

public class Main {
    
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        HashMap<String, String> hashMap = new HashMap<>();
        TreeMap<String, String> treeMap = new TreeMap<>();

        HashMapPrinter hashMapPrinter = new HashMapPrinter();
        hashMapPrinter.printMap(hashMap);
//        hashMapPrinter.printMap(treeMap); (1) ошибка компиляции
//        hashMapPrinter.printMap(map); (2) ошибка компиляции
    }
}

Попробуем понять, почему так происходит. В обоих случаях компилятор не может быть уверенным, что внутри метода HashMapPrinter нет вызовов специфичных для HashMap методов.

TreeMap находится в отдельной ветке реализаций интерфейса Map (смотри иерархию), следовательно, в нем могут отсутствовать некоторые методы, определенные в HashMap (1).

В случае (2), несмотря на то что реальный объект это HashMap, тип его ссылки - Map. Следовательно, у объекта можно будет воспользоваться только методами, определенными в Map, но не в HashMap.

В итоге мы имеем очень простой класс HashMapPrinter, который является слишком специфичным. При таком подходе нам придется создавать Printer для каждой реализации Map.

Программирование на уровне интерфейсов

Часто новичков смущает и путает значение выражения "программирование на уровне интерфейсов". Давайте разберем следующий пример, который немного прояснит ситуацию. Изменим в нашем примере тип аргумента на более общий, которым является Map:

public class MapPrinter {
    
    public void printMap(final Map<?, ?> map) {
        for (final Map.Entry<?, ?> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

Как видно, фактическая реализация не изменилась, а единственное отличие это тип аргумента - теперь это "final Map". Тем самым мы показываем компилятору, что метод не использует никаких специфических для HashMap и других реализаций методов. Вся необходимая функциональность уже была определена в методе entrySet().

Маленькое изменение = большая выгода. Теперь этот класс может работать с любой реализацией Map:

public class Main {
   
   public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        HashMap<String, String> hashMap = new HashMap<>();
        TreeMap<String, String> treeMap = new TreeMap<>();

        MapPrinter mapPrinter = new MapPrinter();
        mapPrinter.printMap(hashMap);
        mapPrinter.printMap(treeMap);
        mapPrinter.printMap(map);
    }
}

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

Где еще использовать интерфейсы

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

public class MapReporter {

    private final Map<?, ?> map;

    public MapReporter(final Map<?, ?> map) {
        this.map = map;
    }

    public void printMap() {
        for (final Map.Entry<?, ?> entry : this.map.entrySet()) {
            System.out.println(entry.getKey() + " " + entry.getValue());
        }
    }
}

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

Заключение

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


Автор - Борис https://t.me/java_wine

Источник: https://habr.com/ru/post/658237/


Интересные статьи

Интересные статьи

Субботний вечер омрачен скандалом - сайт не работает, провайдер негодяй, админы - не специалисты, а сервера - решето. Вызов принят, или почему при всей нелюбви к 1С-Битри...
Всем привет. Если вы когда-либо работали с универсальными списками в Битрикс24, то, наверное, в курсе, что страница детального просмотра элемента полностью идентична странице редак...
Но если для интернет-магазина, разработанного 3–4 года назад «современные» ошибки вполне простительны потому что перед разработчиками «в те далекие времена» не стояло таких задач, то в магазинах, сдел...
На сегодняшний день у сервиса «Битрикс24» нет сотен гигабит трафика, нет огромного парка серверов (хотя и существующих, конечно, немало). Но для многих клиентов он является основным инструментом ...
Основанная в 1998 году компания «Битрикс» заявила о себе в 2001 году, запустив первый в России интернет-магазин программного обеспечения Softkey.ru.