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

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 :) 
....

Но это может не иметь большого смысла :)

Кстати, синхронизировать доступ к нему тоже нужно в основном по той же причине.

OscarRyz
источник
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 нет встроенной концепции неизменяемости. Невозможно пометить методы как мутаторы. Следовательно, в языке нет способа обеспечить неизменность объекта.

Стив Куо
источник