Pourquoi l'objet final peut-il être modifié?

89

Je suis tombé sur le code suivant dans une base de code sur laquelle je travaille:

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

    ...

INSTANCEest déclaré comme final. Pourquoi des objets peuvent-ils être ajoutés INSTANCE? Cela ne devrait-il pas invalider l'utilisation de final. (Ce n'est pas le cas).

Je suppose que la réponse doit faire quelque chose avec des pointeurs et de la mémoire, mais j'aimerais savoir avec certitude.

Matt McCormick
la source
Cette idée fausse revient assez souvent, pas nécessairement sous forme de question. Souvent comme réponse ou commentaire.
Robin
5
Explication simple de JLS: "Si une variable finale contient une référence à un objet, alors l'état de l'objet peut être modifié par des opérations sur l'objet, mais la variable fera toujours référence au même objet." Documentation JLS
realPK

Réponses:

161

finalrend simplement la référence d' objet immuable. L'objet vers lequel il pointe n'est pas immuable en faisant cela. INSTANCEne peut jamais faire référence à un autre objet, mais l'objet auquel il se réfère peut changer d'état.

Sean Owen
la source
1
+1, Pour plus de détails, veuillez consulter java.sun.com/docs/books/jls/second_edition/html/… , section 4.5.4.
Abel Morelos
Alors disons que je désérialise un ConfigurationServiceobjet et que j'essaye de faire un INSTANCE = deserializedConfigurationServicene serait pas autorisé?
diegoaguilar
Vous ne pouvez jamais attribuer de INSTANCEfaire référence à un autre objet. Peu importe d'où vient l'autre objet. (NB il y en a un INSTANCEpar ClassLoaderqui a chargé cette classe. En théorie, vous pourriez charger la classe plusieurs fois dans une JVM et chacune est séparée. Mais c'est un point technique différent.)
Sean Owen
@AkhilGite votre modification de ma réponse a fait une erreur; il a en fait inversé le sens de la phrase, ce qui était correct. La référence est immuable. L'objet reste modifiable. Il ne «devient pas immuable».
Sean Owen
@SeanOwen Sry pour la modification que j'ai faite, votre déclaration est tout à fait exacte et merci.
AkhilGite
33

Être définitif n'est pas la même chose qu'être immuable.

final != immutable

Le finalmot-clé est utilisé pour s'assurer que la référence n'est pas modifiée (c'est-à-dire que la référence qu'elle possède ne peut pas être remplacée par une nouvelle)

Mais si l'attribut self est modifiable, vous pouvez faire ce que vous venez de décrire.

Par exemple

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

Si nous instancions cette classe, nous ne pourrons pas attribuer une autre valeur à l'attribut someFinalObjectcar il est final .

Donc ce n'est pas possible:

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

Mais si l'objet lui-même est mutable comme ceci:

class MutableObject {
     private int n = 0;

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

Ensuite, la valeur contenue par cet objet mutable peut être modifiée.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Cela a le même effet que votre message:

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

Ici, vous ne changez pas la valeur de INSTANCE, vous modifiez son état interne (via la méthode, provider.add)

si vous voulez éviter que la définition de classe ne soit modifiée comme ceci:

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

Mais cela n'a peut-être pas beaucoup de sens :)

En passant, vous devez également synchroniser l'accès à celui-ci pour la même raison.

OscarRyz
la source
26

La clé du malentendu se trouve dans le titre de votre question. Ce n'est pas l' objet qui est définitif, c'est la variable . La valeur de la variable ne peut pas changer, mais les données qu'elle contient le peuvent.

Rappelez-vous toujours que lorsque vous déclarez une variable de type référence, la valeur de cette variable est une référence et non un objet.

Jon Skeet
la source
11

final signifie simplement que la référence ne peut pas être modifiée. Vous ne pouvez pas réaffecter INSTANCE à une autre référence si elle est déclarée comme définitive. L'état interne de l'objet est toujours modifiable.

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

lancerait une erreur de compilation

Théo
la source
7

Une fois qu'une finalvariable a été affectée, elle contient toujours la même valeur. Si une finalvariable contient une référence à un objet, alors l'état de l'objet peut être modifié par des opérations sur l'objet, mais la variable fera toujours référence au même objet. Cela s'applique également aux tableaux, car les tableaux sont des objets; si une finalvariable contient une référence à un tableau, alors les composants du tableau peuvent être modifiés par des opérations sur le tableau, mais la variable fera toujours référence au même tableau.

La source

Voici un guide pour rendre un objet immuable .

défectueux
la source
4

Final et immuable ne sont pas la même chose. Final signifie que la référence ne peut pas être réaffectée, vous ne pouvez donc pas dire

INSTANCE = ...

Immuable signifie que l'objet lui-même ne peut pas être modifié. Un exemple de ceci est la java.lang.Stringclasse. Vous ne pouvez pas modifier la valeur d'une chaîne.

Chris Dail
la source
2

Java n'a pas le concept d'immuabilité intégré au langage. Il n'y a aucun moyen de marquer les méthodes comme un mutateur. Par conséquent, le langage n'a aucun moyen d'imposer l'immuabilité des objets.

Steve Kuo
la source