Warum kann das endgültige Objekt geändert werden?

87

In einer Codebasis, an der ich arbeite, bin ich auf folgenden Code gestoßen:

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

    ...

INSTANCEwird als deklariert final. Warum können Objekte hinzugefügt werden INSTANCE? Sollte dies nicht die Verwendung von final ungültig machen? (Es tut nicht).

Ich gehe davon aus, dass die Antwort etwas mit Zeigern und Gedächtnis zu tun hat, möchte es aber sicher wissen.

Matt McCormick
quelle
Dieses Missverständnis taucht ziemlich oft auf, nicht unbedingt als Frage. Oft als Antwort oder Kommentar.
Robin
5
Einfache Erklärung von JLS: "Wenn eine endgültige Variable einen Verweis auf ein Objekt enthält, kann der Status des Objekts durch Operationen am Objekt geändert werden, aber die Variable verweist immer auf dasselbe Objekt." JLS-Dokumentation
realPK

Antworten:

158

finaleinfach macht das Objekt Bezug unveränderlich. Das Objekt, auf das es zeigt, ist dadurch nicht unveränderlich. INSTANCEkann niemals auf ein anderes Objekt verweisen, aber das Objekt, auf das es verweist, kann den Status ändern.

Sean Owen
quelle
1
+1, Weitere Informationen finden Sie unter java.sun.com/docs/books/jls/second_edition/html/… , Abschnitt 4.5.4.
Abel Morelos
ConfigurationServiceNehmen wir also an, ich deserialisiere ein Objekt und versuche, eine INSTANZ zu machen = deserializedConfigurationServicewäre nicht erlaubt?
Diegoaguilar
Sie könnten niemals zuweisen INSTANCE, auf ein anderes Objekt zu verweisen. Es spielt keine Rolle, woher das andere Objekt kam. (NB es gibt eine INSTANCEpro ClassLoader, die diese Klasse geladen hat. Sie könnten die Klasse theoretisch mehrmals in eine JVM laden und jede ist separat. Aber dies ist ein anderer technischer Punkt.)
Sean Owen
@AkhilGite Ihre Bearbeitung meiner Antwort hat es falsch gemacht; es kehrte tatsächlich den Sinn des Satzes um, was richtig war. Die Referenz ist unveränderlich. Das Objekt bleibt veränderlich. Es wird nicht "unveränderlich".
Sean Owen
@ SeanOwen Sry für die Bearbeitung, die ich gemacht habe, Ihre Aussage ist völlig richtig und Danke.
AkhilGite
33

Endgültig zu sein ist nicht dasselbe wie unveränderlich zu sein.

final != immutable

Das finalSchlüsselwort wird verwendet, um sicherzustellen, dass die Referenz nicht geändert wird (dh die Referenz, die sie hat, kann nicht durch eine neue ersetzt werden).

Wenn das Attribut jedoch selbst geändert werden kann, ist es in Ordnung, das zu tun, was Sie gerade beschrieben haben.

Zum Beispiel

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

Wenn wir diese Klasse instanziieren, können wir dem Attribut keinen anderen Wert zuweisen, someFinalObjectda es endgültig ist .

Das ist also nicht möglich:

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

Aber wenn das Objekt selbst so veränderlich ist:

class MutableObject {
     private int n = 0;

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

Dann kann der in diesem veränderlichen Objekt enthaltene Wert geändert werden.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Dies hat den gleichen Effekt wie Ihr Beitrag:

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

Hier ändern Sie nicht den Wert von INSTANCE, sondern ändern den internen Status (über die Methode providers.add).

Wenn Sie verhindern möchten, dass die Klassendefinition folgendermaßen geändert wird:

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

Aber es könnte nicht viel Sinn machen :)

Aus dem gleichen Grund müssen Sie übrigens auch den Zugriff darauf synchronisieren .

OscarRyz
quelle
25

Der Schlüssel zum Missverständnis liegt im Titel Ihrer Frage. Es ist nicht das Objekt, das endgültig ist, es ist die Variable . Der Wert der Variablen kann sich nicht ändern, die darin enthaltenen Daten jedoch.

Denken Sie immer daran, dass der Wert dieser Variablen beim Deklarieren einer Referenztypvariablen eine Referenz und kein Objekt ist.

Jon Skeet
quelle
11

final bedeutet nur, dass die Referenz nicht geändert werden kann. Sie können INSTANCE nicht einer anderen Referenz zuweisen, wenn diese als endgültig deklariert ist. Der interne Zustand des Objekts ist weiterhin veränderbar.

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

würde einen Kompilierungsfehler auslösen

Das Ö
quelle
7

Sobald eine finalVariable zugewiesen wurde, enthält sie immer denselben Wert. Wenn eine finalVariable einen Verweis auf ein Objekt enthält, kann der Status des Objekts durch Operationen am Objekt geändert werden, die Variable verweist jedoch immer auf dasselbe Objekt. Dies gilt auch für Arrays, da Arrays Objekte sind. Wenn eine finalVariable einen Verweis auf ein Array enthält, können die Komponenten des Arrays durch Operationen am Array geändert werden, die Variable verweist jedoch immer auf dasselbe Array.

Quelle

Hier ist eine Anleitung , wie Sie ein Objekt unveränderlich machen können .

defektiv
quelle
4

Endgültig und unveränderlich sind nicht dasselbe. Final bedeutet, dass die Referenz nicht neu zugewiesen werden kann, sodass Sie nichts sagen können

INSTANCE = ...

Unveränderlich bedeutet, dass das Objekt selbst nicht geändert werden kann. Ein Beispiel hierfür ist die java.lang.StringKlasse. Sie können den Wert einer Zeichenfolge nicht ändern.

Chris Dail
quelle
2

In Java ist das Konzept der Unveränderlichkeit nicht in die Sprache integriert. Es gibt keine Möglichkeit, Methoden als Mutator zu markieren. Daher hat die Sprache keine Möglichkeit, die Unveränderlichkeit von Objekten zu erzwingen.

Steve Kuo
quelle