Как можно сделать класс Java неизменным, зачем нужна неизменяемость и есть ли какие-то преимущества в этом?
@Immutable
аннотацию из jcabi-
Как можно сделать класс Java неизменным, зачем нужна неизменяемость и есть ли какие-то преимущества в этом?
@Immutable
аннотацию из jcabi-
Ответы:
Что такое неизменный объект?
Неизменяемый объект - это объект, который не изменит состояние после создания экземпляра.
Как сделать объект неизменным?
В общем, неизменяемый объект может быть создан путем определения класса, который не имеет открытых членов и не имеет никаких сеттеров.
Следующий класс создаст неизменный объект:
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);
}
В чем преимущество неизменности?
Преимущество неизменности заключается в параллелизме. Трудно поддерживать корректность изменяемых объектов, поскольку несколько потоков могут пытаться изменить состояние одного и того же объекта, что приводит к тому, что некоторые потоки видят разное состояние одного и того же объекта, в зависимости от времени чтения и записи в указанный объект.
Имея неизменяемый объект, можно гарантировать, что все потоки, которые смотрят на объект, будут видеть одно и то же состояние, поскольку состояние неизменяемого объекта не изменится.
return Collections.unmodifiableList(list);
можете вернуть представление списка только для чтения.
final
. В противном случае его можно расширить с помощью метода установки (или других видов методов изменения и изменяемых полей).
final
если у класса есть только private
поля, поскольку к ним нельзя получить доступ из подклассов.
В дополнение к уже приведенным ответам я бы порекомендовал прочитать о неизменяемости в Эффективной Java, 2-е изд., Поскольку есть некоторые детали, которые легко упустить (например, защитные копии). Плюс, Эффективная Java 2-е изд. необходимо прочитать каждому разработчику Java.
Вы делаете класс неизменным следующим образом:
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
(чтобы мы не могли изменить его значение после создания объекта)Неизменяемые классы полезны, потому что
- они потокобезопасны.
- Они также выражают нечто глубокое в вашем дизайне: «Этого нельзя изменить». Когда это применимо, это именно то, что вам нужно.
Неизменяемость может быть достигнута в основном двумя способами:
final
атрибутов экземпляра, чтобы избежать переназначенияПреимущества неизменности - это допущения, которые вы можете сделать для этих объектов:
Неизменяемые классы не могут переназначать значения после создания экземпляра. Конструктор присваивает значения своим частным переменным. Пока объект не станет нулевым, значения не могут быть изменены из-за недоступности методов установки.
чтобы быть неизменным, должно удовлетворять следующее:
/**
* 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;
}
}
Преимущества: неизменяемые объекты содержат свои инициализированные значения до тех пор, пока не умрут.
Неизменяемый класс - это те, чьи объекты нельзя изменить после создания.
Неизменяемые классы полезны для
пример
Класс 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;
}
}
Как сделать 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");
}
}
Зачем нужна неизменяемость и есть ли преимущества в ее использовании?
Ранее опубликованные ответы достаточно хороши, чтобы оправдать необходимость неизменности, и это за
Другой способ сделать неизменяемый объект - использовать библиотеку 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)
}
}
Неизменяемый класс - это просто класс, экземпляры которого нельзя изменить.
Вся информация, содержащаяся в каждом экземпляре, фиксирована на время существования объекта, поэтому никаких изменений невозможно наблюдать.
Неизменяемые классы проще разрабатывать, реализовывать и использовать, чем изменяемые классы.
Чтобы сделать класс неизменяемым, следуйте этим пяти правилам:
Не предоставляйте методы, изменяющие состояние объекта
Убедитесь, что класс нельзя расширить.
Сделайте все поля окончательными.
Сделайте все поля закрытыми.
Обеспечьте монопольный доступ ко всем изменяемым компонентам.
Неизменяемые объекты изначально ориентированы на многопотоковое исполнение; они не требуют синхронизации.
Неизменяемыми объектами можно свободно делиться.
Неизменяемые объекты - отличные строительные блоки для других объектов
@Jack, наличие финальных полей и сеттеров в классе не сделает класс неизменяемым. final только убедитесь, что переменная никогда не переназначается. Вам нужно вернуть глубокую копию всех полей в методах получения. Это гарантирует, что после получения объекта из метода получения внутреннее состояние объекта не будет нарушено.
Как не носитель английского языка, мне не нравится общая интерпретация «неизменяемого класса» как «сконструированные объекты класса неизменяемы»; скорее, я склонен интерпретировать это как «сам объект класса неизменен».
Тем не менее, «неизменяемый класс» - это своего рода неизменяемый объект. Разница при ответе в том, в чем выгода. Насколько мне известно / интерпретация, неизменяемый класс предотвращает изменение поведения своих объектов во время выполнения.
Большинство ответов здесь хороши, и некоторые упоминали правила, но я считаю, что хорошо изложить словами, почему и когда нам нужно следовать этим правилам. Итак, я даю объяснение ниже
И, конечно, если мы попытаемся использовать сеттеры для этих конечных переменных, компилятор выдаст ошибку.
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?
1. Наличие только примитивных членов: у нас нет проблем. Если у класса есть только примитивные члены, то нам не нужно объявлять класс как final.
2. Сохранение объектов в качестве переменных-членов: если у нас есть объекты в качестве переменных-членов, то мы должны сделать члены этих объектов также окончательными. Это означает, что нам нужно пройти вглубь дерева и сделать все объекты / примитивы окончательными, что не всегда возможно. Таким образом, обходной путь - сделать класс окончательным, что предотвращает наследование. Таким образом, нет вопроса о переопределении методов получения подклассом.
Аннотацию @Value Lombok можно использовать для создания неизменяемых классов. Это так же просто, как код ниже.
@Value
public class LombokImmutable {
int id;
String name;
}
Согласно документации на сайте Ломбока:
@Value - неизменный вариант @Data; все поля по умолчанию становятся закрытыми и окончательными, а сеттеры не создаются. Сам класс также становится окончательным по умолчанию, потому что неизменяемость не является чем-то, что можно принудительно перенести на подкласс. Как и @Data, также создаются полезные методы toString (), equals () и hashCode (), каждое поле получает метод получения, а также создается конструктор, охватывающий все аргументы (кроме полей final, которые инициализируются в объявлении поля). .
Полностью рабочий пример можно найти здесь.
record
можно использовать Java ?