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


86

Як можна зробити клас Java незмінним, у чому необхідність незмінності та чи є якась перевага у використанні цього?


2
Існує вступ для незмінного класу: javapractices.com/topic/…
qrtt1

перевірте цю @Immutableанотацію з jcabi-аспектів
yegor256

Відповіді:


135

Що таке незмінний об'єкт?

Незмінним об'єктом є об'єкт, який не змінить стан після його створення.

Як зробити об’єкт незмінним?

Взагалі, незмінний об'єкт можна створити, визначивши клас, у якого не виставлено жодного з його членів і який не має жодних сетерів.

Наступний клас створить незмінний об'єкт:

class ImmutableInt {
  private final int value;

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

  public int getValue() {
    return value;
  }
}

Як видно з наведеного прикладу, значення ImmutableIntcan можна встановити лише тоді, коли об'єкт екземпляром, і, маючи лише 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);
}

У чому перевага незмінності?

Перевага незмінності полягає в одночасності. Важко підтримувати коректність змінних об'єктів, оскільки кілька потоків можуть намагатися змінити стан одного і того ж об'єкта, приводячи до того, що деякі потоки бачать різний стан одного і того ж об'єкта, залежно від часу читання та запису на зазначений об'єкт. об'єкт.

Маючи незмінний об'єкт, можна переконатися, що всі потоки, які дивляться на об'єкт, бачитимуть однаковий стан, оскільки стан незмінного об'єкта не зміниться.


5
Безпека ниток (важлива для одночасності) - не єдина перевага незмінності; це також означає, що вам не потрібно робити захисні копії об'єктів, і це запобігає помилкам, оскільки ви не можете помилково змінити об'єкти, які не повинні змінюватися.
Джеспер

7
Замість того, щоб повертати копію списку, ви також return Collections.unmodifiableList(list);можете повернути список лише для читання.
Jesper

19
Клас теж повинен бути складений final. В іншому випадку його можна розширити за допомогою методу встановлення (або інших видів методів мутації та змінних полів).
Абхінав Саркар

6
@AbhinavSarkar Не повинно бути, finalякщо клас має лише privateполя, оскільки до них неможливо отримати доступ з підкласів.
icza

3
Клас @icza повинен бути остаточним, оскільки у нас є метод public getter, ми можемо розширити клас і перевизначити метод getter, а потім змінити поле по-своєму .. так що це вже не є незмінним
sunil

19

На додаток до вже наведених відповідей, я б рекомендував прочитати про незмінність у Effective Java, 2nd Ed., Оскільки є деякі деталі, які легко пропустити (наприклад, захисні копії). Плюс, Ефективна Java, 2-е видання. є обов'язковим для читання для кожного розробника Java.


3
Це точний ресурс, на який слід подивитися. Згадані переваги варіюються від коду, менш схильного до помилок, до безпеки потоків.
gpampara

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 для всіх змінних у ньому, щоб отримати значення Members
  • Немає методів сетерів

Незмінні класи корисні, оскільки
- Вони безпечні для потоків.
- Вони також висловлюють щось глибоке щодо вашого дизайну: "Не можу цього змінити". Коли це стосується, це саме те, що вам потрібно.


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;
}
}

Переваги: ​​незмінні об'єкти містять свої ініціалізовані значення, поки не помруть.

java-незмінні-класи-коротка примітка


2

Незмінним класом є ті, об’єкти яких не можна змінити після створення.

Незмінні класи корисні для

  • Мета кешування
  • Одночасне середовище (ThreadSafe)
  • Важко успадкувати
  • Значення не можна змінювати в будь-якому середовищі

Приклад

Струнний клас

Приклад коду

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". Це найпростіший і безшвидкий спосіб створення незмінного класу.

Клас запису - це дрібно незмінний , прозорий носій для фіксованого набору полів, відомий як запис, 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 ключове слово переконайтесь, що змінна ніколи не перепризначається. Вам потрібно повернути глибоку копію всіх полів у методах отримання. Це забезпечить, щоб після отримання об’єкта за допомогою методу getter внутрішній стан об’єкта не порушувався.


-1

Мені не є носієм англійської мови, мені не подобається поширене тлумачення поняття "незмінний клас", оскільки "побудовані об'єкти класу є незмінними"; швидше, я сам схиляюсь до інтерпретації цього як "сам об'єкт класу є незмінним".

Тим не менш, "незмінний клас" є свого роду незмінним об'єктом. Різниця полягає у відповіді, яка користь. Наскільки мені відомо / інтерпретація, незмінний клас не дозволяє своїм об'єктам змінювати поведінку під час виконання.


-1

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

  • Оголосити змінні-члени як остаточні - Коли ми оголошуємо їх як остаточні, компілятор змушує нас їх ініціалізувати. Ми можемо ініціалізувати безпосередньо, за замовчуванням, за допомогою конструктора аргументів. (Див. Зразок коду нижче), і після ініціалізації ми не можемо їх змінити, оскільки вони остаточні.
  • І звичайно, якщо ми намагаємось використовувати 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. Розміщення об’єктів як змінних-членів: Якщо ми маємо об’єкти як змінні-члени, тоді ми повинні зробити члени цих об’єктів також остаточними. Означає, що нам потрібно пройти глибоко по дереву і зробити всі об’єкти / примітиви остаточними, що може бути неможливим постійно. Тож обхідний шлях - зробити клас остаточним, що запобігає успадкуванню. Отже, мова не йде про те, щоб підклас перевизначав методи отримання.


-1

Анотація @Value Ломбока може бути використана для створення незмінних класів. Це так само просто, як код нижче.

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

Відповідно до документації на сайті Ломбока:

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

Повний робочий приклад можна знайти тут.


Перш ніж відповісти на старе запитання, отримавши прийняту відповідь (шукайте зелений ✓), а також інші відповіді, переконайтеся, що ваша відповідь додає щось нове або є іншим корисним стосовно них. Будь ласка, будьте обережні, відповідаючи на питання OP. Як можна зробити клас Java незмінним, у чому необхідність незмінності та чи є якась перевага від використання цього? . - Ви даєте лише часткову відповідь, у якій просто зазначено, що можна використовувати Lombok, сторонній фреймворк / бібліотеку, що не обов’язково стосується питання OP. Також вийшла Java 15, навіщо використовувати Lombok, коли recordможна використовувати Java ?
Іво Морі,

Я даю один із варіантів, як цього досягти. Не всі використовують Java 15, сьогодні більшість програм працюють на попередніх версіях, тому ви говорите, чому використовувати Lombok, не має особливого сенсу. У моєму поточному проекті ми нещодавно перейшли на Java 11 і використовуємо Lombok для досягнення незмінних класів. Крім того, моя відповідь є доповненням до того, що відповіді вже були. На те, що є незмінним, уже відповіли, мабуть, ви очікуєте, що я перепишу це лише для завершення.
Анубхав

Справедливо. Я не голосував ні проти, ні проти. Я намагався вказати на причини, чому ще двоє людей не прийняли цю відповідь. Якщо ви і як хочете редагувати свою відповідь, залежить від вас.
Іво Морі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.