Неизменяемый класс?

86

Как можно сделать класс Java неизменным, зачем нужна неизменяемость и есть ли какие-то преимущества в этом?

JavaUser
источник
2
Есть введение для неизменяемого класса: javapractices.com/topic/…
qrtt1
проверьте эту @Immutableаннотацию из jcabi-
Asset Management
См. Также: stackoverflow.com/questions/3769607
Стивен К.

Ответы:

135

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

Неизменяемый объект - это объект, который не изменит состояние после создания экземпляра.

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

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

Следующий класс создаст неизменный объект:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

Как можно увидеть в приведенном выше примере, значение ImmutableIntможет быть установлено только при создании экземпляра объекта, а при наличии только метода getter ( getValue) состояние объекта не может быть изменено после создания экземпляра.

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

Например, разрешение ссылки на массив или ArrayListполучение через геттер позволит внутреннему состоянию измениться путем изменения массива или коллекции:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

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

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

Один из способов обойти эту проблему - вернуть копию массива или коллекции при вызове из геттера:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

В чем преимущество неизменности?

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

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

Coobird
источник
5
Безопасность потоков (важно для параллелизма) - не единственное преимущество неизменности; это также означает, что вам не нужно делать защитные копии объектов, и это предотвращает ошибки, потому что вы не можете по ошибке изменить объекты, которые не должны быть изменены.
Джеспер
7
Вместо того, чтобы возвращать копию списка, вы также return Collections.unmodifiableList(list);можете вернуть представление списка только для чтения.
Jesper
18
Класс тоже надо сделать final. В противном случае его можно расширить с помощью метода установки (или других видов методов изменения и изменяемых полей).
Abhinav Sarkar
6
@AbhinavSarkar Не обязательно, finalесли у класса есть только privateполя, поскольку к ним нельзя получить доступ из подклассов.
icza 03
3
Класс @icza должен быть окончательным, потому что у нас есть общедоступный метод получения, мы можем расширить класс и переопределить метод получения, а затем изменить поле по-своему ... так что он больше не является неизменным
сунил
19

В дополнение к уже приведенным ответам я бы порекомендовал прочитать о неизменяемости в Эффективной Java, 2-е изд., Поскольку есть некоторые детали, которые легко упустить (например, защитные копии). Плюс, Эффективная Java 2-е изд. необходимо прочитать каждому разработчику Java.

Фабиан Штег
источник
3
Это точный ресурс, на который стоит обратить внимание. Упомянутые преимущества варьируются от менее подверженного ошибкам кода до безопасности потоков.
gpampara 02
6

Вы делаете класс неизменным следующим образом:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

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

    // No setter;
}

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

  • Класс должен быть объявлен как final(чтобы дочерние классы не могли быть созданы)
  • Члены класса должны быть объявлены как final(чтобы мы не могли изменить его значение после создания объекта)
  • Напишите методы Getter для всех переменных в нем, чтобы получить значения членов
  • Методы без сеттеров

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

Duffymo
источник
5

Неизменяемость может быть достигнута в основном двумя способами:

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

Преимущества неизменности - это допущения, которые вы можете сделать для этих объектов:

  • вы получаете правило отсутствия побочных эффектов (которое очень популярно на языках функционального программирования) и позволяет вам проще использовать объекты в параллельной среде, поскольку вы знаете, что они не могут быть изменены атомарным или неатомарным способом, когда они используется многими потоками
  • реализации языков могут обрабатывать эти объекты по-разному, помещая их в зоны памяти, которые используются для статических данных, обеспечивая более быстрое и безопасное использование этих объектов (это то, что происходит внутри JVM для строк)
Джек
источник
Неизменяемость - это не то же самое, что и отсутствие равных побочных эффектов. Например, неизменяемый объект может вызывать побочные эффекты, такие как запись в файл. Немного неточно сказать, что создание неизменяемого объекта также избавляет от побочных эффектов.
Grundlefleck
1
@Grundleflek, я думаю, это может быть расслоение волос. Класс не является неизменным, если он изменяет файл журнала как часть своего контракта и этот файл журнала доступен для других классов. Если файл журнала скрыт от других классов, а не является частью контракта класса, тогда класс фактически неизменяем и действительно для всех намерений и целей не имеет побочных эффектов. Введение (без источника) на странице побочного эффекта Википедии гласит: «... выражение считается имеющим побочный эффект, если, помимо создания значения, оно также изменяет какое-то состояние или имеет наблюдаемое взаимодействие с вызывающими функциями».
Джефф Аксельрод
3

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

чтобы быть неизменным, должно удовлетворять следующее:

  • Все переменные должны быть закрытыми .
  • Нет мутатора (сеттеров) не предусмотрено.
  • Избегайте переопределения метода, сделав класс окончательным (Строгая неизменяемость) или окончательными методами (неизменяемость недели).
  • Глубоко клонируйте, если он содержит непримитивные или изменяемые классы.

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

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

Ява-неизменяемые классы-короткие заметки

Канди
источник
2

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

Неизменяемые классы полезны для

  • Цель кеширования
  • Параллельная среда (ThreadSafe)
  • Трудно по наследству
  • Значение нельзя изменить ни в какой среде

пример

Класс String

Пример кода

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

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

    public String getRollNumber() {
        return this.rollNumber;
    }
}
Ясир Шабир Чоудхари
источник
2

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

Из 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");
  }
}

Зачем нужна неизменяемость и есть ли преимущества в ее использовании?

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

Бхану Хойсала
источник
0

Другой способ сделать неизменяемый объект - использовать библиотеку Immutables.org :

Предполагая, что были добавлены необходимые зависимости, создайте абстрактный класс с абстрактными методами доступа. Вы можете сделать то же самое, аннотируя интерфейсы или даже аннотации (@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

Теперь можно сгенерировать, а затем использовать сгенерированную неизменяемую реализацию:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}
Как только
источник
0

Неизменяемый класс - это просто класс, экземпляры которого нельзя изменить.

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

Неизменяемые классы проще разрабатывать, реализовывать и использовать, чем изменяемые классы.

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

  1. Не предоставляйте методы, изменяющие состояние объекта

  2. Убедитесь, что класс нельзя расширить.

  3. Сделайте все поля окончательными.

  4. Сделайте все поля закрытыми.

  5. Обеспечьте монопольный доступ ко всем изменяемым компонентам.

Неизменяемые объекты изначально ориентированы на многопотоковое исполнение; они не требуют синхронизации.

Неизменяемыми объектами можно свободно делиться.

Неизменяемые объекты - отличные строительные блоки для других объектов

сандип ванама
источник
0

@Jack, наличие финальных полей и сеттеров в классе не сделает класс неизменяемым. final только убедитесь, что переменная никогда не переназначается. Вам нужно вернуть глубокую копию всех полей в методах получения. Это гарантирует, что после получения объекта из метода получения внутреннее состояние объекта не будет нарушено.

Амит Тор
источник
-1

Как не носитель английского языка, мне не нравится общая интерпретация «неизменяемого класса» как «сконструированные объекты класса неизменяемы»; скорее, я склонен интерпретировать это как «сам объект класса неизменен».

Тем не менее, «неизменяемый класс» - это своего рода неизменяемый объект. Разница при ответе в том, в чем выгода. Насколько мне известно / интерпретация, неизменяемый класс предотвращает изменение поведения своих объектов во время выполнения.

толстушки
источник
-1

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

  • Объявить переменные-члены как «final» - когда мы объявляем их как final, компилятор заставляет нас инициализировать их. Мы можем инициализировать напрямую конструктор по умолчанию с помощью конструктора arg. (См. Пример кода ниже), и после инициализации мы не можем изменить их, поскольку они являются окончательными.
  • И, конечно, если мы попытаемся использовать сеттеры для этих конечных переменных, компилятор выдаст ошибку.

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

Нужно ли нам объявить класс как final?

  • Без ключевого слова final в объявлении класса класс может быть унаследован. Таким образом, подкласс может переопределять методы получения. Здесь мы должны рассмотреть два сценария:

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

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

Срикант М
источник
-1

Аннотацию @Value Lombok можно использовать для создания неизменяемых классов. Это так же просто, как код ниже.

@Value
public class LombokImmutable {
    int id;
    String name;
}

Согласно документации на сайте Ломбока:

@Value - неизменный вариант @Data; все поля по умолчанию становятся закрытыми и окончательными, а сеттеры не создаются. Сам класс также становится окончательным по умолчанию, потому что неизменяемость не является чем-то, что можно принудительно перенести на подкласс. Как и @Data, также создаются полезные методы toString (), equals () и hashCode (), каждое поле получает метод получения, а также создается конструктор, охватывающий все аргументы (кроме полей final, которые инициализируются в объявлении поля). .

Полностью рабочий пример можно найти здесь.

Анубхав
источник
Прежде чем отвечать на старый вопрос, имеющий принятый ответ (ищите зеленый ✓), а также на другие ответы, убедитесь, что ваш ответ добавляет что-то новое или является полезным по отношению к ним. Будьте осторожны, отвечая на вопрос OP. Как сделать класс Java неизменяемым, зачем нужна неизменяемость и есть ли какие-то преимущества в ее использовании? . - Вы даете только частичный ответ, в котором просто говорится, что можно использовать Lombok, стороннюю структуру / библиотеку, что не обязательно, о чем вопрос OP. Также Java 15 отсутствует, зачем использовать Lombok, если recordможно использовать Java ?
Иво Мори
Я предлагаю один из вариантов, как этого добиться. Не все используют Java 15, большинство приложений сегодня работают на предыдущих версиях, поэтому вы говорите, зачем использовать Lombok, не имеет особого смысла. В моем текущем проекте мы недавно перешли на Java 11 и используем Lombok для создания неизменяемых классов. Кроме того, мой ответ является дополнением к уже имеющимся ответам. На то, что является неизменным, уже был дан ответ, я думаю, вы ожидаете, что я перепишу это просто для завершения.
Анубхав
Справедливо. Я не голосовал ни против, ни за. Я попытался указать причины, по которым двое других проголосовали против этого ответа. Вам решать, если и как вы хотите отредактировать свой ответ.
Иво Мори