Як створити незмінні об'єкти в Java?


83

Як створити незмінні об'єкти в Java?

Які об’єкти слід назвати незмінними?

Якщо у мене є клас із усіма статичними членами, він незмінний?



1
Питання, зв’язане вище, не однакове, але відповіді на це запитання повинні відповісти на всі ваші квестони.
Йоахім Зауер,

Якщо у вашому класі всі статичні члени, він не має стану (жоден екземпляр не має індивідуального стану), і питання про змінність чи незмінність стає спірним.
Себастьян Редл,

чи існує інший спосіб ініціалізації полів, відмінних від конструктора. У мене в класі більше 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 .


а як щодо всіх статичних членів?
Neel Salpe

1
клас для цього не повинен бути остаточним.
Angel O'Sphere

12
@Nilesh: незмінність є властивістю екземплярів . Статичні члени зазвичай не відносяться до жодного окремого екземпляра, тому вони не вступають у картину тут.
Йоахім Зауер,

4
Пункт 15 про незмінність Джошуа Блоха - Немає методів, що змінюють стан, Усі поля остаточні, Усі поля приватні, Переконайтеся, що клас не можна розширити, Забезпечте ексклюзивний доступ до будь-яких змінних компонентів.
nsfyn55

2
@Jaochim - Вони абсолютно є частиною рівняння - Візьміть приклад вище, якщо я додаю змінний статичний член і використовую його у функції додавання ImSoImmutable, у вас така сама проблема. Якщо клас незмінний, тоді всі аспекти повинні бути незмінними.
nsfyn55

14

Тільки не додайте до класу загальнодоступні методи мутатора (сеттера).


а як щодо всіх статичних членів? чи змінюється посилання або стан об'єкта для такого типу об'єктів?
Neel Salpe

7
Не має значення. Якщо ви не можете змінити їх зовні якимось способом, це незмінно.
BalusC

на це неможливо відповісти, оскільки ми не знаємо, що роблять статичні мемери ... ofc вони можуть змінювати приватні поля. Якщо вони це роблять, клас не є незмінним.
Angel O'Sphere

І конструктор класу за замовчуванням повинен бути privateабо клас повинен бути final. Тільки щоб уникнути спадкування. Оскільки успадкування порушує інкапсуляцію.
Талха Ахмед Хан

А як щодо передачі змінного об’єкта, наприклад списку незмінному об’єкту, а потім його зміни ззовні .. це можливо, і з цим слід
боротися,

14

Класи не є незмінними, предмети є.

Незмінні засоби: мій загальнодоступний видимий стан не може змінитися після ініціалізації.

Поля не обов'язково оголошувати остаточними, хоча це може надзвичайно допомогти забезпечити безпеку потоків

Якщо у класі є лише статичні члени, то об'єкти цього класу незмінні, оскільки ви не можете змінити стан цього об'єкта (ви, мабуть, також не можете створити його :))


3
роблячи всі поля статичними, обмежує всі входи до спільного стану, що насправді не є корисним.
aviad

6

Щоб зробити клас незмінним у Java, ви можете

взяти до уваги наступні моменти: 1. Не надайте методів встановлення для модифікації значень будь-яких змінних екземпляра класу.

2. Оголосіть клас як „остаточний” . Це не дозволить будь-якому іншому класу розширити його, а отже, замінити будь-який метод із нього, який міг би змінити значення змінних екземпляра.

3. Оголосіть змінні екземпляра як приватні та остаточні .

4. Ви також можете оголосити конструктор класу приватним і додати фабричний метод для створення екземпляра класу, коли це потрібно.

Ці моменти повинні допомогти !!


3
WRT # 4 Як впливає видимість конструктора на мутабільність? Рядок незмінний, але має кілька загальнодоступних конструкторів.
Райан

Як і те, що сказав @Ryan, те саме стосується змінних, наприклад: навіщо їх оголошувати private?
Імператор MC

Не впевнені, чому ця відповідь має будь-які голоси проти. Ця відповідь неповна. Тут не йдеться про змінні об’єкти, які є важливими для вирішення. Будь ласка, прочитайте пояснення @ nsfyn55 для кращого розуміння.
Ketan R

4

З веб-сайту oracle , як створити незмінні об’єкти в Java.

  1. Не передбачайте методів "setter" - методів, які змінюють поля або об'єкти, на які посилаються поля.
  2. Зробіть усі поля остаточними та приватними.
  3. Не дозволяти підкласам замінювати методи. Найпростіший спосіб зробити це - оголосити клас остаточним. Більш складний підхід полягає в тому, щоб зробити конструктор приватним і побудувати екземпляри в заводських методах.
  4. Якщо поля екземпляра містять посилання на змінні об'єкти, не дозволяйте змінювати ці об'єкти:
    I. Не надайте методів, що змінюють змінні об'єкти.
    II. Не діліться посиланнями на змінні об'єкти. Ніколи не зберігайте посилання на зовнішні, змінні об'єкти, передані конструктору; при необхідності створюйте копії та зберігайте посилання на них. Подібним чином створюйте копії внутрішніх змінних об’єктів, коли це необхідно, щоб уникнути повернення оригіналів у ваших методах.

3

Незмінний об’єкт - це об’єкт, який не змінить свого внутрішнього стану після створення. Вони дуже корисні в багатопотокових програмах, оскільки їх можна спільно використовувати між потоками без синхронізації.

Щоб створити незмінний об'єкт, потрібно дотримуватися кількох простих правил:

1. Не додайте жодного методу встановлення

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

2. Оголосіть усі поля остаточними та приватними

Приватне поле не видно поза класом, тому жодні ручні зміни до нього застосувати не можна.

Оголошення поля остаточним гарантуватиме, що якщо воно посилається на примітивне значення, значення ніколи не зміниться, якщо воно посилається на об'єкт, посилання не може бути змінено. Цього недостатньо для того, щоб об’єкт із лише приватними кінцевими полями не мінявся.

3. Якщо поле є змінним об'єктом, створіть його захисні копії для отримання методів

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

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

Така сама проблема трапляється, якщо ви тримаєте посилання, передане конструктору, оскільки його можна змінити. Отже, утримання посилання на об’єкт, переданий конструктору, може створити змінні об’єкти. Для вирішення цієї проблеми необхідно створити захисну копію параметра, якщо це змінні об'єкти.

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

5. Не дозволяйте підкласам замінювати методи

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

Для вирішення цієї проблеми можна зробити одне з наступного:

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

Якщо ви дотримуєтесь цих простих правил, ви можете вільно ділитися своїми незмінними об'єктами між потоками, оскільки вони безпечні для потоків!

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

  • Незмінні предмети дійсно роблять життя простішим у багатьох випадках. Вони особливо застосовні для типів значень, де об'єкти не мають ідентичності, тому їх можна легко замінити, і вони можуть зробити паралельне програмування безпечнішим та чистішим (більшість загальновідомо важких помилок паралельності в кінцевому рахунку спричинені змінним станом між нитки). Однак для великих та / або складних об'єктів створення нової копії об'єкта для кожної окремої зміни може бути дуже дорогим та / або нудним . А для об’єктів з чітко вираженою ідентичністю зміна існуючих об’єктів набагато простіша та інтуїтивніша, ніж створення нової, модифікованої її копії.
  • Є деякі речі, які ви просто не можете зробити з незмінними об'єктами, наприклад, мають двонаправлені відносини . Як тільки ви встановите значення асоціації для одного об’єкта, це змінює ідентичність. Отже, ви встановлюєте нове значення для іншого об’єкта, і воно також змінюється. Проблема в тому, що посилання на перший об’єкт більше не дійсне, оскільки створено новий екземпляр, який представляє об’єкт із посиланням. Продовження цього просто призведе до нескінченних регресів.
  • Щоб реалізувати двійкове дерево пошуку , вам доведеться щоразу повертати нове дерево: Ваше нове дерево повинно було зробити копію кожного вузла, який був змінений (немодифіковані гілки є спільними). Для вашої функції вставки це не так погано, але для мене все стало досить неефективно, коли я почав працювати над видаленням та перебалансуванням.
  • Hibernate та JPA, по суті, диктують, що ваша система використовує змінні об'єкти, оскільки вся їх передумова полягає в тому, що вони виявляють і зберігають зміни до ваших об'єктів даних.
  • Залежно від мови компілятор може здійснити купу оптимізацій при роботі з незмінними даними, оскільки він знає, що дані ніколи не зміняться. Різноманітні речі пропускаються, що дає величезні переваги в роботі.
  • Якщо поглянути на інші відомі мови JVM ( Scala, Clojure ), змінні об'єкти рідко трапляються в коді, і тому люди починають використовувати їх у сценаріях, коли одного потоку недостатньо.

Немає правильного чи неправильного, це просто залежить від того, що ви віддаєте перевагу. Це лише залежить від ваших уподобань і від того, чого ви хочете досягти (а можливість легко використовувати обидва підходи, не відчужуючи завзятих шанувальників тієї чи іншої сторони, - це святий Грааль, якого шукають деякі мови).


2
  • Не передбачайте методів "setter" - методів, які змінюють поля або об'єкти, на які посилаються поля.
  • Зробіть усі поля остаточними та приватними.
  • Не дозволяти підкласам замінювати методи. Найпростіший спосіб зробити це - оголосити клас остаточним. Більш складний підхід полягає в тому, щоб зробити конструктор приватним і побудувати екземпляри в заводських методах.
  • Якщо поля екземпляра містять посилання на змінні об'єкти, не дозволяйте змінювати ці об'єкти:
    • Не пропонуйте методи, що змінюють змінні об'єкти.
    • Не діліться посиланнями на змінні об'єкти. Ніколи не зберігайте посилання на зовнішні, змінні об'єкти, передані конструктору; при необхідності створюйте копії та зберігайте посилання на них. Подібним чином створюйте копії внутрішніх змінних об’єктів, коли це необхідно, щоб уникнути повернення оригіналів у ваших методах.

2

Перш за все, ви знаєте, навіщо потрібно створювати незмінний об'єкт, і які переваги незмінного об'єкта.

Переваги незмінного об'єкта

Паралельність і багатопотоковість Це автоматично безпечно для потоків, тому проблема синхронізації .... тощо

Не потрібно копіювати конструктор Не потрібно реалізовувати клон. Клас неможливо замінити. Зробити поле приватним і остаточним викликом, щоб повністю побудувати об’єкт за один крок, замість використання конструктора no-Argument

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

будь ласка, дивіться наведений нижче код.

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

2

Мінімізуйте мінливість

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

Незмінні класи JDK: String, примітивні класи в упаковці (класи-обгортки), BigInteger та BigDecimal тощо.

Як зробити клас незмінним?

  1. Не надайте жодних методів, що змінюють стан об’єкта (відомих як мутатори).
  2. Переконайтеся, що клас не можна продовжити.
  3. Зробіть усі поля остаточними.
  4. Зробіть усі поля приватними. Це заважає клієнтам отримати доступ до змінних об'єктів, на які посилаються поля, і безпосередньо змінювати ці об'єкти.
  5. Зробіть захисні копії. Забезпечте ексклюзивний доступ до будь-яких змінних компонентів.

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

Якщо ваш клас має будь-які поля, які посилаються на змінні об'єкти, переконайтеся, що клієнти класу не можуть отримати посилання на ці об'єкти. Ніколи не ініціалізуйте таке поле посиланням на об'єкт, надане клієнтом, або повертайте посилання на об'єкт від доступу.

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

1

об'єкт називається незмінним, якщо його стан неможливо змінити після його створення. Одним з найпростіших способів створення незмінного класу в Java є встановлення всіх його полів остаточними. Якщо вам потрібно написати незмінний клас, який включає змінні класи, такі як "java.util.Date". Щоб зберегти незмінність у таких випадках, рекомендується повернути копію оригінального об'єкта,


Це не те, що повернення копії рекомендується, це необхідно. Але також необхідно, щоб у конструкторі була зроблена захисна копія. В іншому випадку об'єкт можна змінити за кулісами.
Райан

1

Незмінні об'єкти - це ті об'єкти, стан яких неможливо змінити після їх створення, наприклад клас String є незмінним класом. Незмінні об'єкти не можна модифікувати, тому вони також безпечні для потоку при одночасному виконанні.

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

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

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

  • переконайтесь, що клас неможливо замінити
  • зробити всю змінну члена приватною та остаточною
  • не надають методи їх встановлення
  • посилання на об’єкт не повинно просочуватися на етапі будівництва

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());

    }
}

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

Клас запису - це дрібно незмінний , прозорий носій для фіксованого набору полів, відомий як запис, 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");
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.