Неизменяемые и немодифицируемые карты Java: в чем разница?

В этом уроке мы научимся использовать неизменяемую и/или неизменяемую Map в Java. Неизменяемые классы помогают избежать множества проблем проектирования в многопоточных приложениях.

1. Неизменяемые и немодифицируемые карты

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

Map<String, String> mutableMap = new HashMap<>();mutableMap.put("key1", "value1");Map<String, String> unmodifiableMap= Collections.unmodifiableMap(mutableMap);//Throws java.lang.UnsupportedOperationException//unmodifiableMap.put("key2", "value2");//Changes are visible in both mapsmutableMap.put("key2", "value2");System.out.println(unmodifiableMap); //{key1=value1, key2=value2}

Неизменяемые карты гарантируют, что никакие изменения в базовом объекте карты никогда не будут видны. Мы не можем изменить неизменяемые карты — они не оборачивают другую карту — у них есть свои собственные элементы. Это не представления — это структуры данных. Их содержимое никогда не изменится.

Map<String, String> immutableMap = Map.of("key1", "value1");//throws java.lang.UnsupportedOperationExceptionimmutableMap.put("key2", "value2");

2. Неизменяемые карты

Collectors.unmodifiableMap() был представлен в Java 8 как часть изменений лямбда-выражений. Этот статический метод фабрики принимает Map в качестве параметра и возвращает неизменяемое представление типа java.util.Collections$UnmodifiableMap.

Map<Integer, String> mutableMap = new HashMap<>();//add few entriesMap<Integer, String> unmodifiableMap = Collections.unmodifiableMap(mutableMap);

Класс MapUtils Apache Commons Collections также предоставляет аналогичный метод.

Map<String, String> unmodifiable = MapUtils.unmodifiableMap(mutableMap);

3. Неизменяемые карты

3.1 Использование Map.of() – Java 9

Метод Map.of() был представлен в Java 9. Используя этот метод, мы можем создать неизменяемую карту, содержащую ноль или до 10 пар ключ-значение. Созданные карты имеют тип java.util.ImmutableCollections$MapN и будут выдавать исключение NullPointerException, если обнаружат любой нулевой ключ или значение.

var unmodifiableMap = Map.of(1, "Mumbai", 2, "Pune", 3, "Bangalore");var emptyUnmodifiableMap = Map.of();

3.2 Использование ImmutableMap Guava

Поскольку Guava — внешняя библиотека, ее нужно будет добавить в classpath. Если вы используете Maven, добавьте зависимость Guava следующим образом:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency>

ImmutableMap — это иммутабельная реализация Map. Подобно другим иммутабельным классам, она отвергает нулевые значения.

ImmutableMap можно создать следующими способами:

  • с использованием метода copyOf
  • используя метод
  • с помощью конструктора

ImmutableMap.copyOf() принимает карту в качестве входного параметра и создает неизменяемую карту, содержащую записи, аналогичные входной карте.

Map<Integer, String> mutableMap = new HashMap<>();mutableMap.put(1, "Mumbai");mutableMap.put(2, "Pune");mutableMap.put(3, "Bangalore");var immutableMap = ImmutableMap.copyOf(mutableMap);

ImmutableMap.of() похож на Map.of(), за исключением того, что он возвращает неизменяемую Map, либо пустую, либо с максимум 10 парами ключ-значение. Он возвращает экземпляр типа com.google.common.collect.RegularImmutableMap.

var immutableMap = ImmutableMap.of(1, "Mumbai", 2, "Pune", 3, "Bangalore");var emptyImmutableMap = ImmutableMap.of();

ImmutableMap.builder() возвращает конструктор, который помогает создать неизменяемую карту. Используя конструктор, мы можем добавлять дополнительные записи в неизменяемую карту, которые отсутствуют в исходной базовой карте.

Map<Integer, String> mutableMap = new HashMap<>();mutableMap.put(1, "Mumbai");mutableMap.put(2, "Pune");mutableMap.put(3, "Bangalore");var immutableMap = ImmutableMap.builder().putAll(mutableMap).put(4, "Delhi").build();

4. Производительность и эффективность

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

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

С другой стороны, неизменяемые карты создают эффективную копию исходной карты. Когда мы не ожидаем изменения карты или ожидаем, что карта останется неизменной, хорошей практикой будет защитное копирование ее в неизменяемую карту. Это дает гарантию того, что после ее создания никакие изменения не могут произойти с неизменяемой картой, даже если изменится базовая карта.

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

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

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

Прокрутить вверх