Чому кінцевий об'єкт може бути змінений?


89

Я натрапив на такий код у базі коду, над яким я працюю:

public final class ConfigurationService {
    private static final ConfigurationService INSTANCE = new ConfigurationService();
    private List providers;

    private ConfigurationService() {
        providers = new ArrayList();
    }

    public static void addProvider(ConfigurationProvider provider) {
        INSTANCE.providers.add(provider);
    }

    ...

INSTANCEоголошується як final. Чому об’єкти можна додавати INSTANCE? Чи не повинно це зробити недійсним використання final. (Це не так).

Я припускаю, що відповідь пов’язана з вказівниками та пам’яттю, але я хотів би знати це точно.


Ця помилкова думка виникає досить часто, не обов'язково як питання. Часто як відповідь або коментар.
Робін

5
Просте пояснення від JLS: "Якщо кінцева змінна містить посилання на об'єкт, тоді стан об'єкта може бути змінений за допомогою операцій над об'єктом, але змінна завжди буде посилатися на той самий об'єкт". Документація JLS
realPK

Відповіді:


161

finalпросто робить посилання на об'єкт незмінним. Об'єкт, на який він вказує, не незмінний, роблячи це. INSTANCEніколи не може посилатися на інший об'єкт, але об'єкт, на який він посилається, може змінити стан.


1
+1, для отримання детальної інформації перевірте java.sun.com/docs/books/jls/second_edition/html/… , розділ 4.5.4.
Абель Морелос,

Тож, скажімо, я десеріалізую ConfigurationServiceоб’єкт і намагаюся зробити КОНСТРУКЦІЮ = deserializedConfigurationServiceне дозволено?
diegoaguilar

Ви ніколи не можете призначити INSTANCEпосилання на інший об'єкт. Не має значення, звідки взявся інший об’єкт. (Зверніть увагу, що INSTANCEкожен ClassLoaderзавантажує цей клас один. Теоретично ви можете завантажити клас кілька разів в одному JVM, і кожен окремо. Але це інший технічний момент.)
Шон Оуен,

@AkhilGite Ваша редакція моєї відповіді зробила помилку; це насправді змінило сенс речення, що було правильно. Посилання є незмінним. Об'єкт залишається змінним. Він не "стає незмінним".
Шон Оуен,

@SeanOwen Sry за редагування, яке я зробив, Ваше твердження цілком правильне та Дякую.
AkhilGite

33

Бути остаточним - це не те саме, що бути незмінним.

final != immutable

finalВикористовується ключове слово , щоб переконатися , що посилання не змінюється (тобто посилання вона не може бути замінений на новий)

Але, якщо атрибут self можна змінювати, це нормально робити те, що ви щойно описали.

Наприклад

class SomeHighLevelClass {
    public final MutableObject someFinalObject = new MutableObject();
}

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

Отже, це неможливо:

....
SomeHighLevelClass someObject = new SomeHighLevelClass();
MutableObject impostor  = new MutableObject();
someObject.someFinal = impostor; // not allowed because someFinal is .. well final

Але якщо об'єкт, який він сам, можна змінювати так:

class MutableObject {
     private int n = 0;

     public void incrementNumber() {
         n++;
     }
     public String toString(){
         return ""+n;
     }
}  

Тоді значення, що міститься у цьому зміненому об'єкті, може бути змінено.

SomeHighLevelClass someObject = new SomeHighLevelClass();

someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();

System.out.println( someObject.someFinal ); // prints 3

Це має той самий ефект, що і ваша публікація:

public static void addProvider(ConfigurationProvider provider) {
    INSTANCE.providers.add(provider);
}

Тут ви не змінюєте значення INSTANCE, ви змінюєте його внутрішній стан (за допомогою методу provider.add)

якщо ви не хочете, щоб визначення класу змінювалось так:

public final class ConfigurationService {
    private static final ConfigurationService INSTANCE = new ConfigurationService();
    private List providers;

    private ConfigurationService() {
        providers = new ArrayList();
    }
    // Avoid modifications      
    //public static void addProvider(ConfigurationProvider provider) {
    //    INSTANCE.providers.add(provider);
    //}
    // No mutators allowed anymore :) 
....

Але, можливо, це не має великого сенсу :)

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


26

Ключ до непорозуміння - у назві вашого запитання. Це не об’єкт, який є остаточним, це змінна . Значення змінної не може змінюватися, але дані всередині неї можуть.

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


11

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

final ConfigurationService INSTANCE = new ConfigurationService();
ConfigurationService anotherInstance = new ConfigurationService();
INSTANCE = anotherInstance;

викине помилку компіляції


7

Після finalприсвоєння змінної вона завжди містить одне і те ж значення. Якщо finalзмінна містить посилання на об'єкт, тоді стан об'єкта може бути змінений за допомогою операцій над об'єктом, але змінна завжди буде посилатися на той самий об'єкт. Це стосується також масивів, оскільки масиви є об'єктами; якщо finalзмінна містить посилання на масив, тоді компоненти масиву можуть бути змінені за допомогою операцій над масивом, але змінна завжди буде посилатися на один і той же масив.

Джерело

Ось керівництво щодо того, як зробити об’єкт незмінним .


4

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

INSTANCE = ...

Незмінний означає, що сам об'єкт не може бути змінений. Прикладом цього є java.lang.Stringклас. Ви не можете змінити значення рядка.


2

Java не має вбудованої в мову концепції незмінності. Немає можливості позначити методи як мутатора. Тому мова не має можливості забезпечити незмінність об'єкта.

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