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 doit faire quelque chose avec des pointeurs et de la mémoire, mais j'aimerais savoir avec certitude.
final
rend simplement l'objet référence immuable. L'objet sur 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.
Être définitif n'est pas la même chose qu'être immuable.
final != immutable
Le mot clé 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 a ne peut pas être remplacée par une nouvelle)
Mais, si l'attribut est 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 someFinalObject
car il 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 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 modifiez pas la valeur d'INSTANCE, vous modifiez son état interne (via la méthode providers.add)
si vous voulez éviter que la définition de classe 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 :)
Soit dit en passant, vous devez également synchroniser l'accès à celui-ci essentiellement pour la même raison.
La clé du malentendu réside dans le titre de votre question. Ce n'est pas le objet qui est final, c'est le variable. La valeur de la variable ne peut pas changer, mais les données qu'elle contient peuvent.
N'oubliez pas que lorsque vous déclarez une variable de type référence, la valeur de cette variable est une référence, pas un objet.
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 finale. L'état interne de l'objet est toujours modifiable.
final ConfigurationService INSTANCE = new ConfigurationService();
ConfigurationService anotherInstance = new ConfigurationService();
INSTANCE = anotherInstance;
jetterait une erreur de compilation
Une fois qu'une variable
final
a été affectée, elle contient toujours la même valeur. Si une variablefinal
contient une référence à un objet, 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 variablefinal
contient une référence à un tableau, 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.
Voici un guide sur rendre un objet immuable .
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 le Java.lang.String
classe. Vous ne pouvez pas modifier la valeur d'une chaîne.
Java n'a pas le concept d'immuabilité intégré au langage. Il n'y a aucun moyen de marquer des méthodes comme mutateur. Par conséquent, le langage n'a aucun moyen d'imposer l'immuabilité des objets.