Почему конечный объект можно изменить?


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объект и пытаюсь выполнить INSTANCE = deserializedConfigurationServicenot be allowed?
diegoaguilar

Вы никогда не могли назначить INSTANCEссылку на другой объект. Неважно, откуда появился другой объект. (NB есть один INSTANCEна ClassLoaderкоторый загружен этот класс Вы могли бы в теории нагрузки классовые несколько раз в одной виртуальной машины Java и каждый отдельный Но это другой, технический момент...)
Sean Owen

@AkhilGite, что вы изменили мой ответ, сделали это неправильно; он фактически изменил смысл предложения, что было правильным. Ссылка неизменна. Объект остается изменяемым. Он не «становится неизменным».
Шон Оуэн,

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

33

Быть окончательным - не то же самое, что быть неизменным.

final != immutable

finalИспользуется ключевое слово , чтобы убедиться , что ссылка не меняется (то есть ссылка она не может быть заменен на новый)

Но если атрибут является самодифицируемым, можно делать то, что вы только что описали.

Например

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. Внутреннее состояние объекта остается изменчивым.

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.