Por que o object final pode ser modificado?

Eu me deparei com o seguinte código em uma base de código em que estou trabalhando:

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 é declarado como final. Por que objects podem ser adicionados ao INSTANCE? Isso não deve invalidar o uso de final. (Não faz).

Eu estou supondo que a resposta tem que fazer alguma coisa com pointers e memory, mas gostaria de saber com certeza.

‘final’ simplesmente torna a referência do object imutável. O object para o qual ele aponta não é imutável ao fazer isso. INSTANCE nunca pode se referir a outro object, mas o object a que ele se refere pode mudar de estado.

Ser final não é o mesmo que ser imutável.

final != immutable

A palavra-chave final é usada para garantir que a referência não seja alterada (ou seja, a referência não pode ser substituída por uma nova)

Mas, se o atributo for self é modificável, é ok fazer o que você acabou de descrever.

Por exemplo

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

Se instanciarmos essa class, não poderemos atribuir outro valor ao atributo someFinalObject porque é final .

Então isso não é possível:

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

Mas se o object é mutável assim:

 class MutableObject { private int n = 0; public void incrementNumber() { n++; } public String toString(){ return ""+n; } } 

Então, o valor contido pelo object mutável pode ser alterado.

 SomeHighLevelClass someObject = new SomeHighLevelClass(); someObject.someFinal.incrementNumber(); someObject.someFinal.incrementNumber(); someObject.someFinal.incrementNumber(); System.out.println( someObject.someFinal ); // prints 3 

Isso tem o mesmo efeito que sua postagem:

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

Aqui você não está alterando o valor de INSTANCE, você está modificando seu estado interno (via, provider.add method)

se você quiser impedir que a definição da class seja alterada assim:

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

Mas pode não fazer muito sentido 🙂

By the way, você também tem que sincronizar o access a ele basicamente pelo mesmo motivo.

A chave para o mal-entendido está no título da sua pergunta. Não é o object que é final, é a variável . O valor da variável não pode mudar, mas os dados dentro dela podem.

Lembre-se sempre de que, quando você declara uma variável de tipo de referência, o valor dessa variável é uma referência, não um object.

final significa apenas que a referência não pode ser alterada. Você não pode transferir INSTANCE para outra referência se ela for declarada como final. O estado interno do object ainda é mutável.

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

lançaria um erro de compilation

Uma vez que uma variável final tenha sido atribuída, ela sempre contém o mesmo valor. Se uma variável final contém uma referência a um object, o estado do object pode ser alterado por operações no object, mas a variável sempre se referirá ao mesmo object. Isso se aplica também a matrizes, porque matrizes são objects; se uma variável final tiver uma referência a uma matriz, os componentes da matriz poderão ser alterados pelas operações na matriz, mas a variável sempre se referirá à mesma matriz.

Fonte

Aqui está um guia sobre como tornar um object imutável .

Final e imutável não são a mesma coisa. Final significa que a referência não pode ser transferida, então você não pode dizer

 INSTANCE = ... 

Imutável significa que o object em si não pode ser modificado. Um exemplo disso é a class java.lang.String. Você não pode modificar o valor de uma string.

Java não tem o conceito de imutabilidade embutido na linguagem. Não há como marcar methods como um modificador. Portanto, a linguagem não tem como impor a imutabilidade do object.

Se você estiver usando a palavra-chave final para o object, o hashCode nunca será alterado, tornando-o imutável. Será usado no padrão singleton.

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