Як можна зробити клас Java незмінним, у чому необхідність незмінності та чи є якась перевага у використанні цього?
@Immutable
анотацію з jcabi-аспектів
Як можна зробити клас Java незмінним, у чому необхідність незмінності та чи є якась перевага у використанні цього?
@Immutable
анотацію з jcabi-аспектів
Відповіді:
Що таке незмінний об'єкт?
Незмінним об'єктом є об'єкт, який не змінить стан після його створення.
Як зробити об’єкт незмінним?
Взагалі, незмінний об'єкт можна створити, визначивши клас, у якого не виставлено жодного з його членів і який не має жодних сетерів.
Наступний клас створить незмінний об'єкт:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
Як видно з наведеного прикладу, значення ImmutableInt
can можна встановити лише тоді, коли об'єкт екземпляром, і, маючи лише 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
поля, оскільки до них неможливо отримати доступ з підкласів.
На додаток до вже наведених відповідей, я б рекомендував прочитати про незмінність у Effective Java, 2nd Ed., Оскільки є деякі деталі, які легко пропустити (наприклад, захисні копії). Плюс, Ефективна 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;
}
}
Переваги: незмінні об'єкти містять свої ініціалізовані значення, поки не помруть.
Незмінним класом є ті, об’єкти яких не можна змінити після створення.
Незмінні класи корисні для
Приклад
Струнний клас
Приклад коду
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
". Це найпростіший і безшвидкий спосіб створення незмінного класу.
Клас запису - це дрібно незмінний , прозорий носій для фіксованого набору полів, відомий як запис, 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 ключове слово переконайтесь, що змінна ніколи не перепризначається. Вам потрібно повернути глибоку копію всіх полів у методах отримання. Це забезпечить, щоб після отримання об’єкта за допомогою методу getter внутрішній стан об’єкта не порушувався.
Мені не є носієм англійської мови, мені не подобається поширене тлумачення поняття "незмінний клас", оскільки "побудовані об'єкти класу є незмінними"; швидше, я сам схиляюсь до інтерпретації цього як "сам об'єкт класу є незмінним".
Тим не менш, "незмінний клас" є свого роду незмінним об'єктом. Різниця полягає у відповіді, яка користь. Наскільки мені відомо / інтерпретація, незмінний клас не дозволяє своїм об'єктам змінювати поведінку під час виконання.
Більшість відповідей тут хороші, і деякі з них згадували правила, але я вважаю, що добре сказати словами, чому і коли нам потрібно дотримуватися цих правил. Тож я даю пояснення нижче
І звичайно, якщо ми намагаємось використовувати Setters для цих кінцевих змінних, компілятор видає помилку.
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;
}
}
Чи потрібно нам оголошувати клас як "остаточний"?
1. Наявність лише примітивних членів: у нас немає проблем Якщо клас має лише примітивні члени, то нам не потрібно оголошувати клас як остаточний.
2. Розміщення об’єктів як змінних-членів: Якщо ми маємо об’єкти як змінні-члени, тоді ми повинні зробити члени цих об’єктів також остаточними. Означає, що нам потрібно пройти глибоко по дереву і зробити всі об’єкти / примітиви остаточними, що може бути неможливим постійно. Тож обхідний шлях - зробити клас остаточним, що запобігає успадкуванню. Отже, мова не йде про те, щоб підклас перевизначав методи отримання.
Анотація @Value Ломбока може бути використана для створення незмінних класів. Це так само просто, як код нижче.
@Value
public class LombokImmutable {
int id;
String name;
}
Відповідно до документації на сайті Ломбока:
@Value - незмінний варіант @Data; всі поля за замовчуванням зроблені приватними та остаточними, а сетери не генеруються. Сам клас також робиться остаточним за замовчуванням, оскільки незмінність - це не те, що можна нав'язати підкласу. Як і @Data, також генеруються корисні методи toString (), equals () та hashCode (), кожне поле отримує метод getter, а також створюється конструктор, що охоплює кожен аргумент (крім кінцевих полів, які ініціалізовані в оголошенні поля). .
Повний робочий приклад можна знайти тут.
record
можна використовувати Java ?