Неизменяемые объекты в Java

Неизменяемые объекты в Java

1. обзор

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

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

Неизменяемый объект - этоobject whose internal state remains constant after it has been entirely created.

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

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

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

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

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

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

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

В Javavariables are mutable by default, meaning we can change the value they hold.

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

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

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

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

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

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

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

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

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

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

    // ...
}

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

Однако в нашем примере мы гарантируем только то, чтоcurrency не изменится, поэтомуwe must rely on the CurrencyAPI to protect itself from changes.

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

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 имеет только методы, доступные только для чтения.

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

5. Выгоды

Поскольку внутреннее состояние неизменяемого объекта остается постоянным во времени,we can share it safely among multiple threads.

Мы также можем использовать его свободно, и ни один из объектов, ссылающихся на него, не заметит никакой разницы, мы можем сказать, чтоimmutable objects are side-effects free.

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

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

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