Как создать неизменяемые объекты в Java?

83

Как создать неизменяемые объекты в Java?

Какие объекты следует называть неизменяемыми?

Если у меня есть класс со всеми статическими членами, он неизменен?

Нил Салпе
источник
возможный дубликат Что подразумевается под неизменным?
Joachim Sauer
1
Связанный выше вопрос не тот, но ответы на этот вопрос должны отвечать на все ваши квесты.
Joachim Sauer
Если ваш класс - все статические члены, он не имеет состояния (ни один экземпляр не имеет индивидуального состояния), и вопрос об изменяемости или неизменности становится спорным.
Себастьян Редл,
есть ли другой способ инициализировать поля, кроме конструктора. В моем классе более 20 полей. Инициализировать все поля с помощью конструктора очень сложно, некоторые поля даже необязательны.
Nikhil Mishra

Ответы:

88

Ниже приведены жесткие требования к неизменяемому объекту.

  1. Сделать класс финальным
  2. сделать все члены окончательными, установить их явно, в статическом блоке или в конструкторе
  3. Сделать всех участников закрытыми
  4. Нет методов, изменяющих состояние
  5. Будьте предельно осторожны, чтобы ограничить доступ к изменяемым членам (помните, что поле может быть, finalно объект все еще может быть изменяемым, т. Е. private final Date imStillMutable). Вы должны сделать это defensive copiesв этих случаях.

Причины создания класса finalочень тонкие и часто упускаются из виду. Если это не окончательный вариант, люди могут свободно расширять ваш класс, переопределять publicили protectedповедение, добавлять изменяемые свойства, а затем предоставлять свой подкласс в качестве замены. Объявив класс, finalвы можете гарантировать, что этого не произойдет.

Чтобы увидеть проблему в действии, рассмотрим пример ниже:

public class MyApp{

    /**
     * @param args
     */
    public static void main(String[] args){

        System.out.println("Hello World!");

        OhNoMutable mutable = new OhNoMutable(1, 2);
        ImSoImmutable immutable = mutable;

        /*
         * Ahhhh Prints out 3 just like I always wanted
         * and I can rely on this super immutable class 
         * never changing. So its thread safe and perfect
         */
        System.out.println(immutable.add());

        /* Some sneak programmer changes a mutable field on the subclass */
        mutable.field3=4;

        /*
         * Ahhh let me just print my immutable 
         * reference again because I can trust it 
         * so much.
         * 
         */
        System.out.println(immutable.add());

        /* Why is this buggy piece of crap printing 7 and not 3
           It couldn't have changed its IMMUTABLE!!!! 
         */
    }

}

/* This class adheres to all the principles of 
*  good immutable classes. All the members are private final
*  the add() method doesn't modify any state. This class is 
*  just a thing of beauty. Its only missing one thing
*  I didn't declare the class final. Let the chaos ensue
*/ 
public class ImSoImmutable{
    private final int field1;
    private final int field2;

    public ImSoImmutable(int field1, int field2){
        this.field1 = field1;
        this.field2 = field2;
    }

    public int add(){
        return field1+field2;
    }
}

/*
This class is the problem. The problem is the 
overridden method add(). Because it uses a mutable 
member it means that I can't  guarantee that all instances
of ImSoImmutable are actually immutable.
*/ 
public class OhNoMutable extends ImSoImmutable{   

    public int field3 = 0;

    public OhNoMutable(int field1, int field2){
        super(field1, field2);          
    }

    public int add(){
       return super.add()+field3;  
    }

}

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

Вывод заключается в том, что для получения жестких гарантий неизменяемости вы должны пометить класс как final. Это подробно рассматривается в книге Джошуа Блоха « Эффективная Java» и явно упоминается в спецификации модели памяти Java .

nsfyn55
источник
как насчет всех статических членов?
Neel Salpe
1
для этого класс не обязательно должен быть финальным.
Angel O'Sphere
12
@Nilesh: неизменность - это свойство экземпляров . Статические члены обычно не относятся ни к одному экземпляру, поэтому здесь они не фигурируют.
Joachim Sauer
4
Правило 15 Джошуа Блоха о неизменности - Никаких методов, изменяющих состояние, Все поля окончательные, Все поля приватные, Гарантия того, что класс не может быть расширен, Гарантировать монопольный доступ к любым изменяемым компонентам.
nsfyn55
2
@Jaochim - они абсолютно являются частью уравнения - возьмите пример выше, если я добавлю изменяемый статический член и использую его в функции добавления ImSoImmutable, у вас такая же проблема. Если класс неизменен, все аспекты должны быть неизменными.
nsfyn55
14

Только не добавляйте в класс общедоступные методы-мутаторы (сеттеры).

BalusC
источник
как насчет всех статических членов? изменяется ли ссылка или состояние объекта для такого типа объектов?
Neel Salpe
7
Неважно. Если вы не можете изменить их извне каким-либо методом, он неизменен.
BalusC
На этот вопрос нельзя ответить, поскольку мы не знаем, что делают статические члены ... часто они могут изменять приватные поля. Если они это сделают, класс неизменяем.
Angel O'Sphere
И конструктор класса по умолчанию должен быть privateили класс должен быть final. Просто чтобы избежать наследования. Потому что наследование нарушает инкапсуляцию.
Талха Ахмед Хан
Как насчет передачи изменяемого объекта, например List, неизменяемому объекту, а затем его изменению извне ... это возможно, и с этим следует бороться, используя защитные копии во время создания объекта
Ясин Хаджадж
14

Классы не неизменяемы, объекты неизменны.

Неизменяемость означает: мое общедоступное видимое состояние не может измениться после инициализации.

Поля не обязательно объявлять окончательными, хотя это может значительно помочь в обеспечении безопасности потоков.

Если ваш класс имеет только статические члены, то объекты этого класса неизменяемы, потому что вы не можете изменить состояние этого объекта (вы, вероятно, также не можете его создать :))

Александр Погребняк
источник
3
делая все поля статическими, ограничивает все интервалы одним и тем же состоянием, что на самом деле бесполезно.
aviad
6

Чтобы сделать класс неизменяемым в Java, вы можете принять во внимание следующие моменты:

1. Не предоставляйте методы установки для изменения значений любых переменных экземпляра класса.

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

3. Объявите переменные экземпляра как частные и окончательные .

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

Эти моменты должны помочь !!

ВишЭнтузиаст
источник
3
WRT # 4 Как видимость конструктора влияет на изменчивость? Строка неизменна, но имеет несколько общедоступных конструкторов.
Райан
Как сказал @Ryan, то же самое относится и к переменным экземпляра: почему они должны быть объявлены private?
MC Emperor
Не уверен, почему у этого ответа есть положительные голоса. Это неполный ответ. Он не говорит об изменяемых объектах, которые важно решить. Пожалуйста, прочтите объяснение @ nsfyn55 для лучшего понимания.
Ketan R
4

С сайта oracle , как создавать неизменяемые объекты в Java.

  1. Не предоставляйте методы «установки» - методы, которые изменяют поля или объекты, на которые ссылаются поля.
  2. Сделайте все поля окончательными и закрытыми.
  3. Не позволяйте подклассам переопределять методы. Самый простой способ сделать это - объявить класс final. Более сложный подход - сделать конструктор закрытым и создавать экземпляры в фабричных методах.
  4. Если поля экземпляра содержат ссылки на изменяемые объекты, не позволяйте изменять эти объекты:
    I. Не предоставляйте методы, которые изменяют изменяемые объекты.
    II. Не делитесь ссылками на изменяемые объекты. Никогда не храните ссылки на внешние изменяемые объекты, переданные конструктору; при необходимости создайте копии и сохраните ссылки на копии. Точно так же при необходимости создавайте копии своих внутренних изменяемых объектов, чтобы не возвращать оригиналы в ваших методах.
e11438
источник
3

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

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

1. Не добавляйте никаких сеттеров.

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

2. Объявите все поля окончательными и закрытыми

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

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

3. Если поле является изменяемым объектом, создайте его защитные копии для методов получения.

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

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

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

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

5. Не позволяйте подклассам переопределять методы.

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

Чтобы решить эту проблему, можно выполнить одно из следующих действий:

  1. Объявите неизменяемый класс как final, чтобы его нельзя было расширить
  2. Объявите все методы неизменяемого класса final, чтобы их нельзя было переопределить
  3. Создайте частный конструктор и фабрику для создания экземпляров неизменяемого класса, потому что класс с частными конструкторами не может быть расширен

Если вы следуете этим простым правилам, вы можете свободно делиться своими неизменяемыми объектами между потоками, потому что они потокобезопасны!

Ниже приведены несколько примечательных моментов:

  • Неизменяемые объекты действительно во многих случаях упрощают жизнь. Они особенно применимы для типов значений, где объекты не имеют идентичности, поэтому их можно легко заменить, и они могут сделать параллельное программирование более безопасным и чистым (большинство общеизвестно, что трудно найти ошибки параллелизма, в конечном итоге вызваны изменяемым состоянием, разделяемым между потоки). Однако для больших и / или сложных объектов создание новой копии объекта для каждого отдельного изменения может быть очень дорогостоящим и / или утомительным . А для объектов с отличной идентичностью изменение существующих объектов намного проще и интуитивно понятнее, чем создание его новой, измененной копии.
  • Есть некоторые вещи, которые вы просто не можете делать с неизменяемыми объектами, например, иметь двунаправленные отношения . Как только вы установите значение ассоциации для одного объекта, его идентичность изменится. Итак, вы устанавливаете новое значение для другого объекта, и оно также изменяется. Проблема в том, что ссылка на первый объект больше недействительна, потому что был создан новый экземпляр, представляющий объект со ссылкой. Продолжение этого приведет к бесконечным регрессиям.
  • Чтобы реализовать бинарное дерево поиска , вы должны каждый раз возвращать новое дерево: ваше новое дерево должно будет делать копию каждого узла, который был изменен (неизмененные ветви являются общими). Для вашей функции вставки это не так уж плохо, но для меня все быстро стало неэффективным, когда я начал работать над удалением и повторной балансировкой.
  • Hibernate и JPA по существу диктуют, что ваша система использует изменяемые объекты, потому что вся их предпосылка состоит в том, что они обнаруживают и сохраняют изменения в ваших объектах данных.
  • В зависимости от языка компилятор может сделать несколько оптимизаций при работе с неизменяемыми данными, потому что он знает, что данные никогда не изменятся. Пропускаются всевозможные вещи, что дает колоссальный выигрыш в производительности.
  • Если вы посмотрите на другие известные языки JVM ( Scala, Clojure ), изменяемые объекты редко встречаются в коде, и поэтому люди начинают использовать их в сценариях, где однопоточности недостаточно.

Нет правильного или неправильного, все зависит от того, что вы предпочитаете. Это просто зависит от ваших предпочтений и от того, чего вы хотите достичь (а возможность легко использовать оба подхода, не отталкивая стойких фанатов той или иной стороны, является святым Граалем, которого ищут некоторые языки).

amitkumar12788
источник
2
  • Не предоставляйте методы «установки» - методы, которые изменяют поля или объекты, на которые ссылаются поля.
  • Сделайте все поля окончательными и закрытыми.
  • Не позволяйте подклассам переопределять методы. Самый простой способ сделать это - объявить класс final. Более сложный подход - сделать конструктор закрытым и создавать экземпляры в фабричных методах.
  • Если поля экземпляра содержат ссылки на изменяемые объекты, не позволяйте изменять эти объекты:
    • Не предоставляйте методы, которые изменяют изменяемые объекты.
    • Не делитесь ссылками на изменяемые объекты. Никогда не храните ссылки на внешние изменяемые объекты, переданные конструктору; при необходимости создайте копии и сохраните ссылки на копии. Аналогичным образом создавайте копии своих внутренних изменяемых объектов, когда это необходимо, чтобы избежать возврата оригиналов в ваших методах.
Аджай Кумар
источник
2

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

Преимущества неизменяемого объекта

Параллелизм и многопоточность Это автоматически потокобезопасно, поэтому проблема синхронизации ... и т. Д.

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

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

пожалуйста, посмотрите код ниже.

public final class ImmutableReminder{
    private final Date remindingDate;

    public ImmutableReminder (Date remindingDate) {
        if(remindingDate.getTime() < System.currentTimeMillis()){
            throw new IllegalArgumentException("Can not set reminder" +
                    " for past time: " + remindingDate);
        }
        this.remindingDate = new Date(remindingDate.getTime());
    }

    public Date getRemindingDate() {
        return (Date) remindingDate.clone();
    }
}
JBVala
источник
2

Минимизируйте изменчивость

Неизменяемый класс - это просто класс, экземпляры которого нельзя изменить. Вся информация, содержащаяся в каждом экземпляре, предоставляется при его создании и фиксируется на время существования объекта.

Неизменяемые классы JDK: String, упакованные примитивные классы (классы-оболочки), BigInteger и BigDecimal и т. Д.

Как сделать класс неизменным?

  1. Не предоставляйте никаких методов, изменяющих состояние объекта (известных как мутаторы).
  2. Убедитесь, что класс нельзя расширить.
  3. Сделайте все поля окончательными.
  4. Сделайте все поля закрытыми. Это не позволяет клиентам получить доступ к изменяемым объектам, на которые ссылаются поля, и напрямую изменять эти объекты.
  5. Делайте защитные копии. Обеспечьте монопольный доступ ко всем изменяемым компонентам.

    общественный список getList () {return Collections.unmodifiableList (список); <=== защитная копия изменяемого поля перед его возвратом вызывающей стороне}

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

import java.util.Date;
public final class ImmutableClass {

       public ImmutableClass(int id, String name, Date doj) {
              this.id = id;
              this.name = name;
              this.doj = doj;
       }

       private final int id;
       private final String name;
       private final Date doj;

       public int getId() {
              return id;
       }
       public String getName() {
              return name;
       }

     /**
      * Date class is mutable so we need a little care here.
      * We should not return the reference of original instance variable.
      * Instead a new Date object, with content copied to it, should be returned.
      * */
       public Date getDoj() {
              return new Date(doj.getTime()); // For mutable fields
       }
}
import java.util.Date;
public class TestImmutable {
       public static void main(String[] args) {
              String name = "raj";
              int id = 1;
              Date doj = new Date();

              ImmutableClass class1 = new ImmutableClass(id, name, doj);
              ImmutableClass class2 = new ImmutableClass(id, name, doj);
      // every time will get a new reference for same object. Modification in              reference will not affect the immutability because it is temporary reference.
              Date date = class1.getDoj();
              date.setTime(date.getTime()+122435);
              System.out.println(class1.getDoj()==class2.getDoj());
       }
}

Для получения дополнительной информации см. Мой блог:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html

Раджеш Диксит
источник
@Pang есть ли другой способ инициализировать поля, кроме конструктора. В моем классе более 20 полей. Инициализировать все поля с помощью конструктора очень сложно, некоторые поля даже необязательны.
Nikhil Mishra
1
@NikhilMishra, вы можете использовать шаблон проектирования Builder для инициализации переменных во время создания объекта. Вы можете оставить обязательные переменные, которые будут установлены в конструкторе, а остальные необязательные переменные, которые будут установлены с помощью методов установки. Но, строго говоря, таким образом вы не создадите класс True Immutable.
sunny_dev 08
1

объект называется неизменным, если его состояние не может быть изменено после создания. Один из самых простых способов создания неизменяемого класса в Java - установить все его поля как final. Если вам нужно написать неизменяемый класс, который включает в себя изменяемые классы, такие как «java.util.Date». Чтобы сохранить неизменность в таких случаях, рекомендуется вернуть копию исходного объекта,

Рудра
источник
Дело не в том, что рекомендуется возвращать копию, это необходимо. Но также необходимо, чтобы в конструкторе была сделана защитная копия. В противном случае объект может быть изменен за кадром.
Райан
1

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

Особенности неизменяемых классов:

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

Ключи для записи неизменяемого класса:

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

Следующие несколько шагов необходимо учитывать, если вы хотите, чтобы какой-либо класс был неизменным.

  1. Класс должен быть помечен как окончательный
  2. Все поля должны быть закрытыми и окончательными
  3. Замените сеттеры конструктором (для присвоения значения переменной).

Давайте взглянем на то, что мы набрали выше:

//ImmutableClass
package younus.attari;

public final class ImmutableExample {

    private final String name;
    private final String address;

    public ImmutableExample(String name,String address){
        this.name=name;
        this.address=address;
    }


    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

}

//MainClass from where an ImmutableClass will be called
package younus.attari;

public class MainClass {

    public static void main(String[] args) {
        ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
        System.out.println(example.getName());

    }
}
user3205589
источник
0

Обычно игнорируемые, но важные свойства неизменяемых объектов

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

Рассмотрим следующие классы:

public final class ImmutableClass {

  private final MutableClass mc;

  public ImmutableClass(MutableClass mc) {
    this.mc = mc;
  }

  public MutableClass getMutClass() {
    return this.mc;
  }
}

public class MutableClass {

  private String name;

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

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


public class MutabilityCheck {

public static void main(String[] args) {

  MutableClass mc = new MutableClass();

  mc.setName("Foo");

  ImmutableClass iMC = new ImmutableClass(mc);

  System.out.println(iMC.getMutClass().getName());

  mc.setName("Bar");

  System.out.println(iMC.getMutClass().getName());

  }

 }

Ниже будет вывод MutabilityCheck:

 Foo
 Bar

Важно отметить, что

  1. Создание изменяемых объектов на неизменяемом объекте (через конструктор) путем «копирования» или «закрытия» переменных экземпляра неизменяемого объекта, описываемого следующими изменениями:

    public final class ImmutableClass {
    
       private final MutableClass mc;
    
       public ImmutableClass(MutableClass mc) {
         this.mc = new MutableClass(mc);
       }
    
       public MutableClass getMutClass() {
         return this.mc;
       }
    
     }
    
     public class MutableClass {
    
      private String name;
    
      public MutableClass() {
    
      }
      //copy constructor
      public MutableClass(MutableClass mc) {
        this.name = mc.getName();
      }
    
      public String getName() {
        return this.name;
      }
    
      public void setName(String name) {
       this.name = name;
      } 
     }
    

по-прежнему не гарантирует полной неизменяемости, поскольку для класса MutabilityCheck все еще действует следующее:

  iMC.getMutClass().setName("Blaa");
  1. Однако запуск MutabilityCheck с изменениями, внесенными в 1., приведет к следующему результату:

    Foo
    Foo
    
  2. Чтобы достичь полной неизменяемости объекта, все его зависимые объекты также должны быть неизменными.

Сударшан К.Дж.
источник
0

Из JDK 14+, в котором есть JEP 359 , мы можем использовать " records". Это самый простой и легкий способ создания класса Immutable.

Класс записи - это неглубокий неизменяемый прозрачный носитель для фиксированного набора полей, известного как запись, componentsкоторая предоставляет stateописание записи. Каждый componentпорождает finalполе, содержащее предоставленное значение и accessorметод для его получения. Имя поля и имя средства доступа соответствуют имени компонента.

Рассмотрим пример создания неизменяемого прямоугольника.

record Rectangle(double length, double width) {}

Не нужно объявлять конструктор, не нужно реализовывать equals& hashCodeметоды. Просто любой Записи нужно имя и описание состояния.

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

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

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

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

Методы экземпляра

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

статические поля, методы

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

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}
Бхану Хойсала
источник