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

Создать неизменяемый объект – на Java

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

Первоначально опубликовано на www.gunnargissel.com

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

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

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

Вам не нужно верить мне на слово – посмотрите, что эксперты вокруг the |/ web говорят.

Содержание

  1. Создание неизменяемого объекта
  2. Общие ошибки
    1. Примитивы
    2. Сборники
    3. Массивы
    4. Объекты
  3. Как изменить неизменяемый объект
    1. Куда девать строителей?
  4. Обработка неверных данных в неизменяемых объектах
  5. Обертывание

Попались!

Списки, массивы, карты, наборы и другие неизменяемые объекты могут вызывать удивление. Объект private final без установщика привязан к объекту, которому он был первоначально назначен, но значения внутри этого объекта не фиксированы (если только объект не является неизменяемым).

Это означает, что у вас может быть Неизменяемый список покупок ImmutableShoppingList(новая строка[] {"яблоки","анчоусы","макароны"}) и ожидайте, что в списке покупок всегда будут “яблоки”, “анчоусы” и “макароны”.

Кто-нибудь может позвонить my Shopping List.getList()[0]; и изменить ваш список на “шоколадные батончики”, “анчоусы” и “макароны”, что вредно для здоровья и явно не то, что вы хотите.

Примитивы

Хорошие новости! Примитивы неизменяемы, поэтому вам не нужно делать ничего особенного.

Сборники

Хорошие новости! java.util. Collections предоставляет ряд удобных методов, которые упрощают преобразование коллекции в неизменяемую коллекцию.

Проверять:

Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableMap
Collections.unmodifiableNavigableMap
Collections.unmodifiableNavigableSet
Collections.unmodifiableSet
Collections.unmodifiableSortedMap
Collections.unmodifiableSortedSet

Я предлагаю вам сохранить поля как общие коллекции (список, а не ArrayList) и сделать неизменяемыми в конструкторе, например:

public class ImmutableShoppingList {

    private final List list;

    public ImmutableShoppingList(List list){
        this.list = Collections.unmodifiableList(list);
    }

    public List getList(){
        return list;
    }
}

Это позволяет вам использовать генерацию кода IDE для создания геттеров, что приятно, и содержит все модификаторы ввода в одном месте, что тоже приятно.

Плохие новости! Если вы цепляетесь за ссылку на коллекцию при создании коллекции, вы все равно можете изменить ее, даже если вы сохраняете ее как неизменяемую коллекцию внутри. Вот пример:

List originalList = new ArrayList<>();
theList.add("apple");
ImmutableShoppingList blah = new ImmutableShoppingList(originalList);
originalList.add("candy bar");

Предположительно неизменяемый список покупок начинался с яблока, а после создания к нему добавлялся шоколадный батончик. Что мы можем с этим поделать?

Клонируйте список!

public class ImmutableShoppingList {

    private final List list;

    public ImmutableShoppingList(List list){
        List tmpListOfHolding = new ArrayList<>();
        tmpListOfHolding.addAll(list);
        this.list = Collections.unmodifiableList(tmpListOfHolding);
    }

    public String[] getList(){
        return (String[]) list.toArray();
    }
}

Когда мы создаем неизменяемый объект, мы глубоко клонируем коллекцию, что разрывает связь с исходной ссылкой. Теперь, когда мы запускаем пример “украдкой вставить моноблок”, “моноблок” добавляется в исходный список , но не тот Неизменяемый список покупок .

Массивы

Плохие новости! В Java нет никаких удобных методов для предотвращения изменения массивов. Лучше всего либо скрыть исходный массив и всегда возвращать клон, либо не использовать массивы в базовой реализации и вместо этого преобразовать объект коллекции в массив.

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

public class ImmutableShoppingList {

    private final List list;

    public ImmutableShoppingList(String[] list){
        this.list = Collections.unmodifiableList(Arrays.asList(list));
    }

    public String[] getList(){
        return (String[]) list.toArray();
    }
}

Объекты

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

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

Часто вы в конечном итоге работаете с уже существующими изменяемыми объектами либо в вашей кодовой базе, либо в библиотеках. В этом случае мне хотелось бы создать неизменяемый класс-оболочку объекта, который расширяет изменяемый класс. Я нахожу, что статический метод getInstance(Mutable Object obj) может быть полезен, но конструктор Неизменяемый объект (MutableObject obj) также полезен.

А Как Насчет Того, Когда Я Хочу Изменить Неизменяемый Объект?

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

В этом случае я тянусь к шаблону builder.

Шаблон builder создает временный объект с теми же полями, что и у нужного объекта. В нем есть геттеры и сеттеры для всех полей. Он также имеет метод build() , который создает нужный объект

Представьте себе небольшой неизменяемый объект:

class ImmutableDog {
    private final String name;
    private final int weight

    public ImmutableDog(String name, int weight){
        this.name = name;
        this.weight = weight;
    }

    public String getName(){
        return this.name;
    }

    public int getWeight(){
        return this.weight;
    }
}

Вот как выглядел бы строитель:

class ImmutableDogBuilder {
    private String name;
    private int weight;

    public ImmutableDogBuilder(){}

    public ImmutableDog build(){
        return new ImmutableDog(name, weight);
    }

    public ImmutableDogBuilder setName(String name){
        this.name = name;
        return this;
    }

    public ImmutableDogBuilder setWeight(int weight){
        this.weight = weight;
        return this;
    }

    public String getName(){
        return this.name;
    }

    public int getWeight(){
        return this.weight;
    }
}

Обратите внимание на поселенцев Мне очень нравится этот шаблон возврата this для каждого сеттера в классах builder, потому что он создает очень плавный api. Вы могли бы использовать этот ImmutableDogBuilder следующим образом:

ImmutableDogBuilder dogBuilder = new ImmutableDogBuilder().setName("Rover").setWeight(25);

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

Куда Девать Строителя?

Здесь есть две школы мысли.

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

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

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

А Как Насчет Неизменяемых Объектов С Неверными Данными?

Это случается со всеми, особенно если вы принимаете входные данные. Неверные данные!

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

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

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

Мой вторичный подход заключается в том, чтобы встроить небольшое количество бизнес-логики в неизменяемый объект. Я не разрешаю, чтобы обязательные поля были нулевыми, и любое поле, допускающее значение null, является Необязательным .

Например:

class ImmutableDog {
    private final String name;
    private final Optional weight

    public ImmutableDog(String name, Optional weight){
        Objects.requireNonNull(name);
        this.name = name;
        this.weight = weight;
    }

    public String getName(){
        return this.name;
    }

    public Optional getWeight(){
        return this.weight;
    }
}

Это Неизменяемая собака , для которой требуется имя, но не требуется вес. Важно знать, как называть собаку, но не обязательно знать, что Пушистый весит 15 фунтов.

Objects.requireNonNull немедленно выдаст исключение NullPointerException , если имя не указано. Это предотвращает создание бессмысленного неизменяемого объекта. Это также позволяет пользователям неизменяемого объекта (например, потоков или функций) пропускать обработку нулей. Здесь нет нулей.

Использование Необязательного заставляет потребителей Неизменяемого Dog немедленно осознавать, что им, возможно, придется обрабатывать значение null. Предоставление Необязательного api предоставляет нижестоящим пользователям простой и функциональный способ обработки нулей.

Обертывание

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

Основными принципами, которые следует иметь в виду, являются:

  1. Клонируйте массивы, коллекции и объекты, внутренние для вашего неизменяемого объекта
  2. Используйте конструкторы, когда вам нужен изменяемый объект
  3. Используйте Необязательно, чтобы указать поля, допускающие обнуление, в api вашего объекта
  4. Быстрый сбой из-за плохих данных – Objects.requireNonNull может помочь

Идите вперед и прекратите мутировать

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

Оригинал: “https://dev.to/monknomo/make-an-immutable-object—in-java-480n”