Почему конечный объект может быть изменен? - PullRequest
79 голосов
/ 12 марта 2010

В кодовой базе, над которой я работаю, я обнаружил следующий код:

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? Не должно ли это сделать недействительным использование финала. (Это не так).

Я предполагаю, что ответ должен быть связан с указателями и памятью, но хотел бы знать наверняка.

Ответы [ 7 ]

148 голосов
/ 12 марта 2010

final просто делает объект ссылка неизменным. При этом объект, на который он указывает, не является неизменным. INSTANCE никогда не может ссылаться на другой объект, но объект, на который он ссылается, может изменить состояние.

33 голосов
/ 12 марта 2010

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

final != immutable

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

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

Например,

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

Если мы создадим экземпляр этого класса, мы не сможем присвоить другое значение атрибуту someFinalObject, потому что это final .

Так что это невозможно:

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

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

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

24 голосов
/ 12 марта 2010

Ключ к недоразумению находится в заголовке вашего вопроса. Это не объект , который является окончательным, это переменная . Значение переменной не может измениться, но данные внутри нее могут.

Всегда помните, что когда вы объявляете переменную ссылочного типа, значение этой переменной является ссылкой, а не объектом.

11 голосов
/ 12 марта 2010

final означает, что ссылка не может быть изменена. Вы не можете переназначить INSTANCE на другую ссылку, если она объявлена ​​как окончательная. Внутреннее состояние объекта все еще изменчиво.

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

выдаст ошибку компиляции

6 голосов
/ 12 марта 2010

После присвоения переменной final она всегда содержит одно и то же значение. Если переменная final содержит ссылку на объект, то состояние объекта может быть изменено операциями над объектом, но переменная всегда будет ссылаться на один и тот же объект. Это относится и к массивам, потому что массивы являются объектами; если переменная final содержит ссылку на массив, то компоненты массива могут быть изменены операциями над массивом, но переменная всегда будет ссылаться на один и тот же массив.

Источник

Вот руководство по созданию объекта неизменным .

4 голосов
/ 12 марта 2010

Окончательный и неизменный не одно и то же. Окончательный означает, что ссылка не может быть переназначена, поэтому вы не можете сказать

INSTANCE = ...

Неизменяемый означает, что сам объект не может быть изменен. Примером этого является класс java.lang.String. Вы не можете изменить значение строки.

2 голосов
/ 12 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...