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
Комментарии (13)
Naf2000
30.03.2022 18:15+7Ожидал про Map и HashMap, а прочёл про интерфейс и реализацию. Название не соответствует мне кажется.
yapoxe
30.03.2022 18:46-4Интерфейс HashMap неприменим к Map и TreeMap, а интерфейс Map применим ко всем своим реализациям. Я удивляюсь, что про это можно целую статью написать.
upagge
30.03.2022 20:49+12Мой тебе совет, не выкладывай ты это на хабр, уже второй твой пост за сегодня. Тут такие статьи не прокатят. Заведи свой бложик, развивай его, будет тебе еще опыт разворачивания своего сайта. На хабре тебя за такие статьи только минусить будут))
BlackSCORPION
31.03.2022 08:04+3Где то мы свернули ни туда, одни новости, переводы, реклама за редким исключением. Наверное из-за этой ориентации на "прокатывание" а не на полезность для сообщества, всех уровней.
sbv239 Автор
31.03.2022 10:54-2Спасибо за совет. Меня ни минусы ни гневные комментарии совсем не смущают. У меня есть популярный блог на Пикабу по узкопрофессиональной теме и там "хейта" с головой хватает :)
sng8675
31.03.2022 09:59+3Ждём статью про то, чем отличается List от ArrayList
loco777
31.03.2022 16:23-1Лучше уж такие статьи чем переводы сомнительной полезности да и к тому же часто даже не вычетанные. Кторые тяжело прочитать не то что понять. Иногда проскакивае такие тексты что создается впечатление что людям платят за колличество слов в статье, а не за смысл. И еще раз повторюсь целиком и полностью поддерживаю такой формат так как он будет полезен людям начинающим изучать как в этом случае, Java.
Ritan
02.04.2022 12:57Ждём статьи "отличие языка программирования от Java" и " чем процессор отличается от Intel i486"
GaDzik
Буду следить за статьями.