93 votes

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

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

    ...

INSTANCE est 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 a quelque chose à voir avec les pointeurs et la mémoire, mais j'aimerais en être sûr.

0 votes

Cette idée fausse revient assez souvent, pas nécessairement sous forme de question. Souvent sous forme de réponse ou de commentaire.

5 votes

Une explication simple de JLS : "Si une variable finale contient une référence à un objet, alors l'état de cet 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

174voto

Sean Owen Points 36577

final rend simplement l'objet référence immuable. L'objet vers lequel il pointe n'est pas immuable en faisant cela. INSTANCE ne peut jamais faire référence à un autre objet, mais l'objet auquel il fait référence peut changer d'état.

1 votes

+1, Pour plus de détails, veuillez consulter java.sun.com/docs/books/jls/second_edition/html/ , section 4.5.4.

0 votes

Donc disons que je désérialise une ConfigurationService et j'essaye de faire un INSTANCE = deserializedConfigurationService ne serait pas autorisé ?

0 votes

Vous ne pourriez jamais assigner INSTANCE pour faire référence à un autre objet. L'origine de cet autre objet n'a pas d'importance. (NB : il existe un INSTANCE par ClassLoader qui a chargé cette classe. Vous pourriez en théorie charger la classe plusieurs fois dans une JVM et chacune est séparée. Mais ceci est un point différent, technique).

35voto

OscarRyz Points 82553

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

final != immutable

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

Mais, si l'attribut est modifiable, il est possible de 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 someFinalObject parce que c'est final .

Ce n'est donc 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 dans 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 poste :

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

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

si vous voulez empêcher cela, la définition de la classe doit être 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 pourrait ne pas avoir beaucoup de sens :)

Au fait, vous devez aussi synchroniser son accès essentiellement pour la même raison.

27voto

Jon Skeet Points 692016

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

N'oubliez jamais 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.

13voto

Theo Points 221

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

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

entraînerait une erreur de compilation

5voto

Chris Dail Points 11406

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 cela est le java.lang.String classe. Vous ne pouvez pas modifier la valeur d'une chaîne de caractères.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X