JavaRush /Курсы /JAVA 25 SELF /Mutable vs Immutable коллекции: отличие, применение

Mutable vs Immutable коллекции: отличие, применение

JAVA 25 SELF
34 уровень , 3 лекция
Открыта

1. Введение

Если говорить по-простому, mutable-коллекция — это та, которую можно менять после создания: добавлять, удалять и изменять элементы. Immutable (неизменяемая) коллекция — это коллекция, которую после создания изменить нельзя. Прямо как бетон после застывания: можно смотреть, можно трогать, но лепить новые фигурки уже не выйдет.

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

Примеры изменяемых (mutable) коллекций

В Java практически все стандартные коллекции по умолчанию изменяемы. Это такие классы, как:

  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • HashMap
  • LinkedHashMap
  • и многие другие

Пример: ArrayList

import java.util.*;

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.set(1, "Charlie"); // заменили Bob на Charlie
names.remove("Alice");   // удалили Alice
System.out.println(names); // [Charlie]

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

2. Примеры неизменяемых (immutable) коллекций

С этими ребятами, которые появились в Java 9, вы уже тоже успели познакомиться, но к ним ещё нужно привыкнуть:

  • List.of(...)
  • Set.of(...)
  • Map.of(...)
  • List.copyOf(collection)
  • Set.copyOf(collection)
  • Map.copyOf(map)

Пример: List.of

List<String> planets = List.of("Mercury", "Venus", "Earth", "Mars");
System.out.println(planets); // [Mercury, Venus, Earth, Mars]
planets.add("Jupiter"); // Бросит UnsupportedOperationException!

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

Пример: Collections.unmodifiableList

List<String> modifiable = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
unmodifiable.add("c"); // UnsupportedOperationException!

Но здесь есть подвох: если вы измените исходную коллекцию, обёртка тоже изменится!

modifiable.add("c");
System.out.println(unmodifiable); // [a, b, c] — элемент появился!

3. Основные различия между mutable и immutable коллекциями

Свойство Mutable (изменяемые) Immutable (неизменяемые)
Можно добавить элемент? Да Нет
Можно удалить элемент? Да Нет
Можно изменить элемент? Да (например, set) Нет
Потокобезопасность Нет (по умолчанию) Да (нет состояния — нечего менять)
Можно добавить null? Да (обычно) Нет (в фабричных методах Java 9+)
Реализация ArrayList, HashSet и др. List.of, Set.of, Map.of, copyOf

4. Зачем вообще нужны неизменяемые коллекции?

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

Безопасность и защита от ошибок

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

Пример:

public class Team {
    private final List<String> players;

    public Team(List<String> players) {
        // Делаем неизменяемую копию, чтобы никто не мог подменить состав
        this.players = List.copyOf(players);
    }

    public List<String> getPlayers() {
        return players;
    }
}

Теперь любой код, получивший список игроков, не сможет добавить туда своего "друга".

Потокобезопасность

Изменяемые коллекции небезопасны при доступе из нескольких потоков одновременно. Неизменяемые коллекции, наоборот, можно свободно передавать между потоками — никто не сможет их испортить.

Упрощение отладки

Если коллекция не меняется, вы всегда знаете, что в ней лежит. Не надо бояться, что кто-то её "тихо" поменял в другом месте кода.

Использование как ключи или значения в других коллекциях

Неизменяемые объекты — идеальные кандидаты для использования в качестве ключей в Map или элементов в Set. Если объект может измениться после добавления, вы рискуете потерять к нему доступ (см. про hashCode и equals).

5. Когда лучше использовать изменяемые коллекции?

Изменяемые коллекции хороши, когда:

  • Коллекция строится поэтапно, в цикле или из разных источников.
  • Необходимы частые изменения: добавление, удаление, сортировка.
  • Коллекция предназначена для внутреннего использования и никто "снаружи" не сможет её испортить.

Пример: Построение списка

List<String> shoppingList = new ArrayList<>();
shoppingList.add("Молоко");
shoppingList.add("Хлеб");
shoppingList.add("Яблоки");
// После построения — можно сделать неизменяемую версию
List<String> finalList = List.copyOf(shoppingList);

6. Когда лучше использовать неизменяемые коллекции?

  • Для хранения константных данных (например, список дней недели).
  • Для передачи коллекций между слоями приложения (например, из DAO в сервис).
  • Для возврата коллекций из методов, чтобы защитить их от изменений.
  • Для многопоточных сценариев, где важна безопасность.

Пример: Константные данные

public static final List<String> WEEKDAYS = List.of(
    "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
);

Пример: Передача наружу

public List<String> getReadOnlyNames() {
    return List.copyOf(names); // никто не сможет изменить список
}

7. Особенности и подводные камни

Неизменяемость ≠ Потокобезопасность

Неизменяемая коллекция защищена от изменений, но это не значит, что она защищена от других видов проблем в многопоточной среде (например, если элементы коллекции — изменяемые объекты).

List<List<String>> listOfLists = List.of(new ArrayList<>());
listOfLists.get(0).add("Oops!"); // Можно изменить внутренний список!

Обёртки vs копии

Как уже обсуждалось ранее, Collections.unmodifiableList — это обёртка, и если исходную коллекцию изменить, обёртка тоже изменится. А вот List.copyOf создаёт настоящую независимую копию.

List<String> base = new ArrayList<>(List.of("a", "b"));
List<String> wrap = Collections.unmodifiableList(base);
List<String> copy = List.copyOf(base);

base.add("c");
System.out.println(wrap); // [a, b, c] — изменилось!
System.out.println(copy); // [a, b] — осталось прежним!

NullPointerException

Фабричные методы (List.of, Set.of, Map.of) не позволяют добавлять null:

List<String> bad = List.of("a", null); // Бросит NullPointerException уже при создании!

8. Сравнение подходов: плюсы и минусы

Изменяемые коллекции

Главное достоинство изменяемых коллекций — это их гибкость. Вы можете добавлять и удалять элементы прямо на ходу, перестраивать коллекцию по мере развития логики программы. Такой подход особенно удобен, когда нужно быстро собрать временную структуру или что-то динамически менять.

Но за удобство приходится платить. Изменяемая коллекция всегда несёт в себе риск случайного вмешательства: кто-то в коде может нечаянно изменить данные, и это приведёт к трудноуловимым ошибкам. В многопоточных программах ситуация ещё хуже — параллельные изменения легко приводят к гонкам и неожиданным сбоям. Контролировать жизненный цикл таких данных тоже сложнее: нужно постоянно помнить, кто и когда может изменить коллекцию.

Неизменяемые коллекции

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

Минус в том, что иногда придётся жертвовать производительностью. Если нужно добавить новый элемент, приходится создавать новую копию коллекции, что влечёт за собой дополнительные затраты памяти и времени. Кроме того, собирать большие и сложные структуры сразу в неизменяемом виде бывает неудобно. Обычно их строят во временной изменяемой коллекции, а когда всё готово — "замораживают".

9. Типичные ошибки

Ошибка № 1: Возврат изменяемой коллекции наружу. Если вы возвращаете из метода обычный ArrayList, любой внешний код может добавить или удалить элементы. Это может привести к багам, которые очень сложно отследить.

Ошибка № 2: Использование обёртки вместо копии. Если вы используете Collections.unmodifiableList, но исходная коллекция где-то ещё изменяется, "неизменяемость" — иллюзия.

Ошибка № 3: Изменяемые объекты внутри immutable-коллекций. Даже если сама коллекция неизменяема, её элементы могут быть изменяемыми. Это может привести к неожиданным изменениям состояния.

Ошибка № 4: Попытка добавить null в коллекцию, созданную через фабричные методы. В отличие от старых коллекций, новые фабричные методы не позволяют добавлять null — получите NullPointerException.

Ошибка № 5: Ожидание конкретной реализации. Коллекции, созданные через List.of или Set.of, не гарантируют тип реализации (это не обязательно ArrayList или HashSet). Не полагайтесь на это.

1
Задача
JAVA 25 SELF, 34 уровень, 3 лекция
Недоступна
Инвентарь животных: живой список или статичный снимок? 🐾
Инвентарь животных: живой список или статичный снимок? 🐾
1
Задача
JAVA 25 SELF, 34 уровень, 3 лекция
Недоступна
Плейлисты: набор неизменяем, но песни внутри меняются 🎶
Плейлисты: набор неизменяем, но песни внутри меняются 🎶
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ