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

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

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

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

Ответ 1

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

Ответ 2

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

final != immutable

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

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

Например

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

Но если объект it self изменен следующим образом:

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

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

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

Ответ 3

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

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

Ответ 4

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

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

приведет к ошибке компиляции

Ответ 5

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

Источник

Здесь руководство по делает объект неизменным.

Ответ 6

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

INSTANCE = ...

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

Ответ 7

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

Ответ 8

Если вы используете конечное ключевое слово для объекта, hashCode никогда не будет изменяться, что делает его неизменным. Он будет использоваться в одноэлементном шаблоне.

class mysingleton1{

private mysingleton1(){}
public static final mysingleton1 instance= new mysingleton1();

    public static mysingleton1 getInstance(){

        return instance;

    }

}