Рубрики
Без рубрики

Вступающий в силу Java Вторник! Минимизировать Изменчивость

Погружение в семнадцатую главу Эффективной Java. Помеченный java, эффективный, неизменяемый, архитектура.

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

Концепция неизменяемого объекта проста, это объект, который нельзя изменить после его создания. В библиотеках платформы Java есть несколько примеров неизменяемых классов, таких как Строка , Большой десятичный , BigInteger и упакованные примитивные типы. Так почему же вы решили сделать что-то неизменным? Разве удаление возможностей – это не плохо? Что ж, как это часто бывает интересно, лишение возможности что-то делать на самом деле может сделать другие вещи намного проще и безопаснее. Прежде чем перейти к тому, что дает нам неизменяемость, давайте поговорим о том, как сделать класс неизменяемым.

  1. Не предоставляйте методы для изменения состояния: Часто называемые мутаторами или сеттерами.
  2. Убедитесь, что классы не могут быть расширены Чаще всего это достигается путем проведения выпускного класса.
  3. Сделайте все поля окончательными: Четко выражает желание, чтобы внутреннее состояние не было изменено. Также помогает при передаче состояния из одного потока в другой.
  4. Сделайте все поля закрытыми: Вы, скорее всего, сделаете это, если будете следовать другим рекомендациям Эффективная Java и здесь мы следуем этому руководству по тем же причинам.
  5. Обеспечьте эксклюзивный доступ к изменяемому состоянию: Если ваш неизменяемый объект обеспечивает изменяемое состояние с помощью средства доступа, он должен защитить свое внутреннее состояние путем создания защитных копий.

Давайте рассмотрим пример:

@Getter
public final class ComplexNumber {
  private final double real;
  private final double imaginary;

  public ComplexNumber(double real, double imaginary) {
    this.real = real;
    this.imaginary = imaginary;
  }

  public ComplexNumber plus(ComplexNumber other) {
     return new ComplexNumber(this.real + other.real, this.imaginary + other.imaginary);
  }

  public ComplexNumber minus(ComplexNumber other) {
     return new ComplexNumber(this.real - other.real, this.imaginary - other.imaginary);
  }

  // ... other methods
}

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

Теперь давайте перейдем к тому, какие преимущества мы получаем при использовании неизменности.

  • Неизменяемые объекты просты: Ваш объект может находиться только в одном состоянии. Это может значительно упростить работу с вашим объектом.
  • Неизменяемые объекты по своей сути потокобезопасны: Невозможно испортить состояние между потоками. Неизменяемые объекты на сегодняшний день являются самым простым способом обеспечения потокобезопасности.
  • Неизменяемые объекты могут свободно использоваться совместно: Поскольку состояние не может измениться, нет никаких проблем с совместным использованием неизменяемых объектов. Если ваш объект представляет собой виджет прямо сейчас он по-прежнему будет представлять собой Виджет и в будущем. Это означает, что нам не нужно делать защитные копии. Это также позволяет кэшировать обычно создаваемые объекты и просто возвращать их каждый раз, когда они запрашиваются (возможно, со статической фабрикой). Это может ускорить создание объектов, а также уменьшить нагрузку на память.
  • Вы можете не только свободно делиться объектами, но и внутренними компонентами: Это кажется скорее нишевым товаром, но это действительно кажется интересным. Поскольку состояние не меняется, вы можете совместно использовать внутреннее состояние между различными объектами. Пример, приведенный в книге, состоит в том, что BigInteger объект имеет знаковый бит, чтобы указать, представляет ли он положительное или отрицательное число. Если вы хотите отрицать объект, все, что вам нужно сделать, это создать новый BigInteger с перевернутым битом знака, а затем ссылается на остальную часть состояния исходного объекта.

Таким образом, у неизменяемых объектов есть много преимуществ. Как насчет недостатков и как мы можем смягчить эти недостатки?

Изменение чего-либо в объекте требует создания нового объекта: Создание множества новых объектов только для того, чтобы немного изменить состояние, может быть дорогостоящим, особенно если объекты большие. Это может стать намного хуже, если для изменения состояния потребуется несколько шагов, поскольку это приведет к созданию множества потенциально дорогостоящих объектов.

Так как же мы можем смягчить это? Если есть общие операции, которые пользователь может захотеть выполнить с вашим объектом, вы можете выполнить для них многоступенчатый процесс. Это отлично работает, если вы заранее знаете, какие операции пользователи захотят выполнить. А что, если ты этого не сделаешь? Другой вариант, который у вас есть, – создать изменяемый сопутствующий класс, в котором пользователь может выполнить мутацию, которую затем можно использовать для создания неизменяемого класса. Примером этого может быть класс StringBuilder и это способность эффективно создавать Строковые объекты.

Давайте обсудим несколько последних вещей.

Одна из альтернатив сделать ваш класс окончательным – создать частные конструкторы и вместо этого зависеть от статических фабрик для создания ваших неизменяемых объектов. Это дает вам возможность в будущем подклассировать класс, а также оптимизировать производительность позже.

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

Как всегда, я хотел бы упомянуть, как Ломбок может помочь нам достичь наших целей. Неизменяемые объекты ничем не отличаются. Ломбок включает @Значение аннотацию, которую вы добавляете в свой класс, которая сделает ваш класс окончательным , создаст конструктор, который принимает все ваши частные конечные переменные-члены, не генерирует установщики, а также генерирует для вас многие другие стандартные элементы ( Хэш-код , равен , toString ). Хотя простое добавление этой аннотации волшебным образом не сделает ваш класс неизменяемым, это поможет вам хорошо подготовиться к работе с шаблоном класса в неизменяемой совместимости.

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

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

Оригинал: “https://dev.to/kylec32/effective-java-tuesday-minimize-mutability-55db”