«1. Обзор

В этом руководстве мы узнаем, что делает объект неизменяемым, как добиться неизменности в Java и какие преимущества дает это.

2. Что такое неизменяемый объект?

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

Это означает, что публичный API неизменяемого объекта гарантирует нам, что он будет вести себя одинаково в течение всей своей жизни.

Если мы посмотрим на класс String, то увидим, что даже когда его API, кажется, предоставляет нам изменяемое поведение с помощью метода replace, исходная строка не меняется:

String name = "baeldung";
String newName = name.replace("dung", "----");

assertEquals("baeldung", name);
assertEquals("bael----", newName);

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

3. Ключевое слово final в Java

Перед тем, как пытаться добиться неизменности в Java, мы должны поговорить о ключевом слове final.

В Java переменные по умолчанию изменяемы, то есть мы можем изменить значение, которое они содержат.

Используя ключевое слово final при объявлении переменной, компилятор Java не позволит нам изменить значение этой переменной. Вместо этого он сообщит об ошибке времени компиляции:

final String name = "baeldung";
name = "bael...";

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

final List<String> strings = new ArrayList<>();
assertEquals(0, strings.size());
strings.add("baeldung");
assertEquals(0, strings.size());

Второй assertEquals завершится ошибкой, потому что добавление элемента в список изменяет его размер, поэтому он не является неизменяемым объектом.

4. Неизменяемость в Java

Теперь, когда мы знаем, как избежать изменения содержимого переменной, мы можем использовать его для создания API неизменяемых объектов.

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

Шаг вперед в правильном направлении — использовать final при объявлении его атрибутов:

class Money {
    private final double amount;
    private final Currency currency;

    // ...
}

Обратите внимание, что Java гарантирует нам, что значение суммы не изменится, это относится ко всем переменным примитивного типа.

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

Большую часть времени нам нужны атрибуты объекта для хранения пользовательских значений, а местом для инициализации внутреннего состояния неизменяемого объекта является его конструктор:

class Money {
    // ...
    public Money(double amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public Currency getCurrency() {
        return currency;
    }

    public double getAmount() {
        return amount;
    }
}

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

Using the reflection API, we can break immutability and change immutable objects. However, reflection violates immutable object’s public API, and usually, we should avoid doing this.

5. Преимущества

Поскольку внутреннее состояние неизменяемого объекта остается постоянным во времени, мы можем безопасно делиться им между несколькими потоками.

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

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

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

Вы можете найти примеры, использованные в этой статье, на GitHub.