Por que o objeto final pode ser modificado?

88

Encontrei 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 os objetos podem ser adicionados INSTANCE? Isso não deveria invalidar o uso de final. (Não faz).

Presumo que a resposta tenha a ver com ponteiros e memória, mas gostaria de saber com certeza.

Matt McCormick
fonte
Esse equívoco surge com frequência, não necessariamente como uma pergunta. Freqüentemente como uma resposta ou comentário.
Robin
5
Explicação simples de JLS: "Se uma variável final contém uma referência a um objeto, então o estado do objeto pode ser alterado por operações no objeto, mas a variável sempre se referirá ao mesmo objeto." Documentação JLS
realPK

Respostas:

159

finalsimplesmente torna a referência do objeto imutável. O objeto para o qual ele aponta não é imutável ao fazer isso. INSTANCEnunca pode se referir a outro objeto, mas o objeto ao qual se refere pode mudar de estado.

Sean Owen
fonte
1
+1, Para obter mais detalhes, verifique java.sun.com/docs/books/jls/second_edition/html/… , seção 4.5.4.
Abel Morelos
Então digamos que eu desserialize um ConfigurationServiceobjeto e tente fazer um INSTANCE = deserializedConfigurationServicenão seria permitido?
diegoaguilar de
Você nunca poderia atribuir INSTANCEpara se referir a outro objeto. Não importa de onde veio o outro objeto. (NB, há um INSTANCEpor ClassLoaderque carregou esta classe. Você poderia, em teoria, carregar a classe várias vezes em um JVM e cada um é separado. Mas este é um ponto técnico diferente.)
Sean Owen
@AkhilGite sua edição em minha resposta que está errada; na verdade, inverteu o sentido da frase, o que estava correto. A referência é imutável. O objeto permanece mutável. Não "se torna imutável".
Sean Owen
@SeanOwen Sry pela edição que fiz, sua declaração está totalmente certa e obrigado.
AkhilGite de
33

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

final != immutable

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

Mas, se o atributo for self-modificável, não há problema em fazer o que você acabou de descrever.

Por exemplo

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

Se instanciarmos essa classe, não poderemos atribuir outro valor ao atributo someFinalObjectporque ele é final .

Portanto, 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 próprio objeto é mutável assim:

class MutableObject {
     private int n = 0;

     public void incrementNumber() {
         n++;
     }
     public String toString(){
         return ""+n;
     }
}  

Então, o valor contido por aquele objeto 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 (por meio do método provider.add)

se você quiser evitar que a definição da classe 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 :)

A propósito, você também precisa sincronizar o acesso a ele basicamente pelo mesmo motivo.

OscarRyz
fonte
26

A chave para o mal-entendido está no título da sua pergunta. Não é o objeto 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 objeto.

Jon Skeet
fonte
11

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

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

iria lançar um erro de compilação

Theo
fonte
7

Depois de finalatribuída uma variável, ela sempre contém o mesmo valor. Se uma finalvariável contém uma referência a um objeto, o estado do objeto pode ser alterado por operações no objeto, mas a variável sempre se referirá ao mesmo objeto. Isso também se aplica a arrays, porque arrays são objetos; se uma finalvariável contém uma referência a uma matriz, então os componentes da matriz podem ser alterados por operações na matriz, mas a variável sempre se referirá à mesma matriz.

Fonte

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

defeituoso
fonte
4

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

INSTANCE = ...

Imutável significa que o próprio objeto não pode ser modificado. Um exemplo disso é a java.lang.Stringclasse. Você não pode modificar o valor de uma string.

Chris Dail
fonte
2

Java não possui o conceito de imutabilidade embutido na linguagem. Não há como marcar métodos como modificadores. Portanto, a linguagem não tem como impor a imutabilidade do objeto.

Steve Kuo
fonte