«1. Обзор

В этом руководстве мы узнаем о различных способах инициализации HashMap в Java.

Мы будем использовать Java 8, а также Java 9.

2. Статический инициализатор для статического HashMap

Мы можем инициализировать HashMap с помощью статического блока кода:

public static Map<String, String> articleMapOne;
static {
    articleMapOne = new HashMap<>();
    articleMapOne.put("ar01", "Intro to Map");
    articleMapOne.put("ar02", "Some article");
}

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

Давайте продолжим и проверим это:

@Test
public void givenStaticMap_whenUpdated_thenCorrect() {
    
    MapInitializer.articleMapOne.put(
      "NewArticle1", "Convert array to List");
    
    assertEquals(
      MapInitializer.articleMapOne.get("NewArticle1"), 
      "Convert array to List");  
}

Мы также можем инициализировать карту, используя синтаксис с двойными скобками:

Map<String, String> doubleBraceMap  = new HashMap<String, String>() {{
    put("key1", "value1");
    put("key2", "value2");
}};

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

3. Использование коллекций Java

Если нам нужно создать одноэлементную неизменяемую карту с одной записью, Collections.singletonMap() становится очень полезной:

public static Map<String, String> createSingletonMap() {
    return Collections.singletonMap("username1", "password1");
}

Обратите внимание, что карта здесь неизменяемая, и если мы пытаемся добавить больше записей, это вызовет исключение java.lang.UnsupportedOperationException.

Мы также можем создать неизменяемую пустую карту с помощью Collections.emptyMap():

Map<String, String> emptyMap = Collections.emptyMap();

4. Способ Java 8

В этом разделе давайте рассмотрим способы инициализации карты с помощью Java 8. Потоковое API.

4.1. Использование Collectors.toMap()

Давайте воспользуемся потоком двумерного массива строк и соберем их в карту:

Map<String, String> map = Stream.of(new String[][] {
  { "Hello", "World" }, 
  { "John", "Doe" }, 
}).collect(Collectors.toMap(data -> data[0], data -> data[1]));

Обратите внимание, что здесь тип данных ключа и значения карты одинаковы.

Чтобы сделать его более общим, давайте возьмем массив объектов и выполним ту же операцию:

 Map<String, Integer> map = Stream.of(new Object[][] { 
     { "data1", 1 }, 
     { "data2", 2 }, 
 }).collect(Collectors.toMap(data -> (String) data[0], data -> (Integer) data[1]));

В результате мы создадим карту ключа в виде строки и значения в виде целого числа.

4.2. Использование потока Map.Entry

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

Во-первых, давайте воспользуемся реализацией SimpleEntry интерфейса Entry:

Map<String, Integer> map = Stream.of(
  new AbstractMap.SimpleEntry<>("idea", 1), 
  new AbstractMap.SimpleEntry<>("mobile", 2))
  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Теперь давайте создадим карту, используя реализацию SimpleImmutableEntry:

Map<String, Integer> map = Stream.of(
  new AbstractMap.SimpleImmutableEntry<>("idea", 1),    
  new AbstractMap.SimpleImmutableEntry<>("mobile", 2))
  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

4.3. Инициализация неизменяемой карты

В некоторых случаях нам нужно инициализировать неизменяемую карту. Это можно сделать, обернув Collectors.toMap() внутри Collectors.collectingAndThen(): создан только для инициализации карты.

Map<String, String> map = Stream.of(new String[][] { 
    { "Hello", "World" }, 
    { "John", "Doe" },
}).collect(Collectors.collectingAndThen(
    Collectors.toMap(data -> data[0], data -> data[1]), 
    Collections::<String, String> unmodifiableMap));

5. Путь Java 9

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

Давайте продолжим и рассмотрим эти фабричные методы.

5.1. Map.of()

Этот фабричный метод не принимает аргументов, имеет один аргумент и переменные аргументы:

Обратите внимание, что этот метод поддерживает максимум 10 пар ключ-значение.

Map<String, String> emptyMap = Map.of();
Map<String, String> singletonMap = Map.of("key1", "value");
Map<String, String> map = Map.of("key1","value1", "key2", "value2");

5.2. Map.ofEntries()

Он похож на Map.of(), но не имеет ограничений на количество пар ключ-значение:

Обратите внимание, что фабричные методы создают неизменяемые карты, поэтому любая мутация будет результатом в UnsupportedOperationException.

Map<String, String> map = Map.ofEntries(
  new AbstractMap.SimpleEntry<String, String>("name", "John"),
  new AbstractMap.SimpleEntry<String, String>("city", "budapest"),
  new AbstractMap.SimpleEntry<String, String>("zip", "000000"),
  new AbstractMap.SimpleEntry<String, String>("home", "1231231231")
);

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

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

6. Использование Guava

Map<String, String> map = new HashMap<String, String> (
  Map.of("key1","value1", "key2", "value2"));
Map<String, String> map2 = new HashMap<String, String> (
  Map.ofEntries(
    new AbstractMap.SimpleEntry<String, String>("name", "John"),    
    new AbstractMap.SimpleEntry<String, String>("city", "budapest")));

Поскольку мы рассмотрели способы использования ядра Java, давайте двигаться вперед и инициализировать карту с помощью библиотеки Guava:

Это создаст неизменяемую карту, а для создания изменяемой:

Map<String, String> articles 
  = ImmutableMap.of("Title", "My New Article", "Title2", "Second Article");

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

Map<String, String> articles 
  = Maps.newHashMap(ImmutableMap.of("Title", "My New Article", "Title2", "Second Article"));

7. Заключение

ImmutableMap.of("key1", "value1", "key2", "value2");

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

Как всегда, пример исходного кода находится в проекте Github. Примеры Java 9 находятся здесь, а образец Guava — здесь.

«