Perché l'oggetto finale può essere modificato?


89

Mi sono imbattuto nel seguente codice in una base di codice su cui sto lavorando:

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è dichiarato come final. Perché si possono aggiungere oggetti INSTANCE? Questo non dovrebbe invalidare l'uso di final. (Non è così).

Presumo che la risposta abbia a che fare con i puntatori e la memoria, ma vorrei saperlo con certezza.


Questo malinteso emerge abbastanza spesso, non necessariamente come una domanda. Spesso come risposta o commento.
Robin

5
Semplice spiegazione da JLS: "Se una variabile finale contiene un riferimento a un oggetto, lo stato dell'oggetto può essere modificato dalle operazioni sull'oggetto, ma la variabile farà sempre riferimento allo stesso oggetto." Documentazione JLS
realPK

Risposte:


161

finalsemplicemente rende immutabile il riferimento all'oggetto . L'oggetto a cui punta non è immutabile in questo modo. INSTANCEnon può mai fare riferimento a un altro oggetto, ma l'oggetto a cui si riferisce può cambiare stato.


1
+1, per maggiori dettagli si prega di controllare java.sun.com/docs/books/jls/second_edition/html/… , sezione 4.5.4.
Abel Morelos

Quindi diciamo che deserializzo un ConfigurationServiceoggetto e provo a fare un INSTANCE = deserializedConfigurationServicenon sarebbe consentito?
diegoaguilar

Non potresti mai assegnare un INSTANCEriferimento a un altro oggetto. Non importa da dove provenga l'altro oggetto. (NB ce n'è uno INSTANCEper ClassLoaderche ha caricato questa classe. In teoria potresti caricare la classe più volte in una JVM e ognuna è separata. Ma questo è un punto tecnico diverso.)
Sean Owen

@AkhilGite la tua modifica alla mia risposta ha reso sbagliata; effettivamente ha invertito il senso della frase, il che era corretto. Il riferimento è immutabile. L'oggetto rimane mutevole. Non "diventa immutabile".
Sean Owen,

@SeanOwen Sry per la modifica che ho fatto, la tua dichiarazione è completamente corretta e grazie.
AkhilGite

33

Essere definitivi non è la stessa cosa che essere immutabile.

final != immutable

La finalparola chiave viene utilizzata per assicurarsi che il riferimento non venga modificato (ovvero, il riferimento che ha non può essere sostituito con uno nuovo)

Ma, se l'attributo è self è modificabile, va bene fare ciò che hai appena descritto.

Per esempio

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

Se istanziamo questa classe, non saremo in grado di assegnare un altro valore all'attributo someFinalObjectperché è definitivo .

Quindi questo non è possibile:

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

Ma se l'oggetto stesso è mutabile in questo modo:

class MutableObject {
     private int n = 0;

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

Quindi, il valore contenuto da quell'oggetto mutabile può essere modificato.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Questo ha lo stesso effetto del tuo post:

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

Qui non stai cambiando il valore di INSTANCE, stai modificando il suo stato interno (tramite il metodo provider.add)

se vuoi evitare che la definizione della classe debba essere modificata in questo modo:

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

Ma potrebbe non avere molto senso :)

A proposito, devi anche sincronizzare l'accesso ad esso fondamentalmente per lo stesso motivo.


26

La chiave del malinteso è nel titolo della tua domanda. Non è l' oggetto che è finale, è la variabile . Il valore della variabile non può cambiare, ma i dati al suo interno possono.

Ricorda sempre che quando dichiari una variabile del tipo di riferimento, il valore di quella variabile è un riferimento, non un oggetto.


11

final significa semplicemente che il riferimento non può essere modificato. Non puoi riassegnare INSTANCE a un altro riferimento se è dichiarato come finale. Lo stato interno dell'oggetto è ancora mutevole.

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

genererebbe un errore di compilazione


7

Una volta finalassegnata, una variabile contiene sempre lo stesso valore. Se una finalvariabile contiene un riferimento a un oggetto, lo stato dell'oggetto può essere modificato dalle operazioni sull'oggetto, ma la variabile farà sempre riferimento allo stesso oggetto. Questo vale anche per gli array, perché gli array sono oggetti; se una finalvariabile contiene un riferimento a un array, i componenti dell'array possono essere modificati dalle operazioni sull'array, ma la variabile farà sempre riferimento allo stesso array.

fonte

Ecco una guida su come rendere immutabile un oggetto .


4

Finale e immutabile non sono la stessa cosa. Finale significa che il riferimento non può essere riassegnato quindi non puoi dirlo

INSTANCE = ...

Immutabile significa che l'oggetto stesso non può essere modificato. Un esempio di questo è la java.lang.Stringclasse. Non è possibile modificare il valore di una stringa.


2

Java non ha il concetto di immutabilità integrato nel linguaggio. Non è possibile contrassegnare i metodi come mutatori. Pertanto il linguaggio non ha modo di imporre l'immutabilità degli oggetti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.