¿Por qué se puede modificar el objeto final?


89

Encontré el siguiente código en una base de código en la que estoy trabajando:

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);
    }

    ...

INSTANCEse declara como final. ¿Por qué se pueden agregar objetos INSTANCE? ¿No debería invalidar el uso de final. (No lo hace).

Supongo que la respuesta tiene que ver con los punteros y la memoria, pero me gustaría saberlo con certeza.


Este concepto erróneo surge con bastante frecuencia, no necesariamente como una pregunta. A menudo como respuesta o comentario.
Robin

5
Explicación simple de JLS: "Si una variable final contiene una referencia a un objeto, entonces el estado del objeto puede cambiarse mediante operaciones en el objeto, pero la variable siempre se referirá al mismo objeto". Documentación de JLS
realPK

Respuestas:


161

finalsimplemente hace que la referencia del objeto sea invariable. El objeto al que apunta no es inmutable al hacer esto. INSTANCEnunca puede hacer referencia a otro objeto, pero el objeto al que se refiere puede cambiar de estado.


1
+1, para obtener más detalles, consulte java.sun.com/docs/books/jls/second_edition/html/… , sección 4.5.4.
Abel Morelos

Entonces, digamos que deserializo un ConfigurationServiceobjeto e intento hacer un INSTANCE = deserializedConfigurationService¿no estaría permitido?
diegoaguilar

Nunca podría asignar INSTANCEuna referencia a otro objeto. No importa de dónde venga el otro objeto. (Nota: hay uno INSTANCEpor cada ClassLoaderque ha cargado esta clase. En teoría, podría cargar la clase varias veces en una JVM y cada una está separada. Pero este es un punto técnico diferente).
Sean Owen

@AkhilGite, tu edición de mi respuesta lo hizo mal; en realidad invirtió el sentido de la oración, lo cual era correcto. La referencia es inmutable. El objeto permanece mutable. No se "vuelve inmutable".
Sean Owen

@SeanOwen Sry por la edición que hice, su declaración es completamente correcta y gracias.
AkhilGite

33

Ser definitivo no es lo mismo que ser inmutable.

final != immutable

La finalpalabra clave se usa para asegurarse de que la referencia no se cambie (es decir, la referencia que tiene no se puede sustituir por una nueva)

Pero, si el atributo es self es modificable, está bien hacer lo que acaba de describir.

Por ejemplo

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

Si instanciamos esta clase, no podremos asignar otro valor al atributo someFinalObjectporque es final .

Entonces esto no es posible:

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

Pero si el objeto en sí mismo es mutable así:

class MutableObject {
     private int n = 0;

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

Entonces, el valor contenido por ese objeto mutable puede cambiarse.

SomeHighLevelClass someObject = new SomeHighLevelClass();

someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();

System.out.println( someObject.someFinal ); // prints 3

Esto tiene el mismo efecto que tu publicación:

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

Aquí no está cambiando el valor de INSTANCE, está modificando su estado interno (a través del método Suppliers.add)

si desea evitar que la definición de la clase deba cambiarse así:

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

Pero, puede que no tenga mucho sentido :)

Por cierto, también tienes que sincronizar el acceso básicamente por la misma razón.


26

La clave del malentendido está en el título de su pregunta. No es el objeto lo que es final, es la variable . El valor de la variable no puede cambiar, pero los datos que contiene sí pueden.

Recuerde siempre que cuando declara una variable de tipo de referencia, el valor de esa variable es una referencia, no un objeto.


11

final solo significa que la referencia no se puede cambiar. No puede reasignar INSTANCE a otra referencia si está declarada como final. El estado interno del objeto sigue siendo mutable.

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

arrojaría un error de compilación


7

Una vez finalque se ha asignado una variable, siempre contiene el mismo valor. Si una finalvariable tiene una referencia a un objeto, entonces el estado del objeto puede cambiarse mediante operaciones en el objeto, pero la variable siempre se referirá al mismo objeto. Esto también se aplica a las matrices, porque las matrices son objetos; si una finalvariable contiene una referencia a una matriz, entonces los componentes de la matriz pueden cambiarse mediante operaciones en la matriz, pero la variable siempre se referirá a la misma matriz.

Fuente

Aquí hay una guía sobre cómo hacer que un objeto sea inmutable .


4

Final e inmutable no son lo mismo. Final significa que la referencia no se puede reasignar, por lo que no se puede decir

INSTANCE = ...

Inmutable significa que el objeto en sí no se puede modificar. Un ejemplo de esto es la java.lang.Stringclase. No puede modificar el valor de una cadena.


2

Java no tiene el concepto de inmutabilidad integrado en el lenguaje. No hay forma de marcar métodos como mutantes. Por lo tanto, el lenguaje no tiene forma de imponer la inmutabilidad del objeto.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.