Dlaczego można zmodyfikować ostateczny obiekt?


89

W bazie kodu, nad którą pracuję, natknąłem się na następujący kod:

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

    ...

INSTANCEjest zadeklarowany jako final. Dlaczego można dodawać obiekty INSTANCE? Nie powinno to unieważniać użycia wersji ostatecznej. (Tak nie jest).

Zakładam, że odpowiedź ma coś wspólnego ze wskaźnikami i pamięcią, ale chciałbym wiedzieć na pewno.


To błędne przekonanie pojawia się dość często, niekoniecznie jako pytanie. Często jako odpowiedź lub komentarz.
Robin

5
Proste wyjaśnienie z JLS: „Jeśli zmienna końcowa zawiera odniesienie do obiektu, stan obiektu może zostać zmieniony przez operacje na obiekcie, ale zmienna będzie zawsze odnosić się do tego samego obiektu”. Dokumentacja JLS
realPK

Odpowiedzi:


161

finalpo prostu sprawia, że odniesienie do obiektu jest niezmienne. W ten sposób obiekt, na który wskazuje, nie jest niezmienny. INSTANCEnigdy nie może odnosić się do innego obiektu, ale obiekt, do którego się odnosi, może zmienić stan.


1
+1, Więcej informacji można znaleźć w java.sun.com/docs/books/jls/second_edition/html/… , sekcja 4.5.4.
Abel Morelos

Więc powiedzmy, że deserializuję ConfigurationServiceobiekt i próbuję wykonać INSTANCJĘ = deserializedConfigurationServicenie byłoby to dozwolone?
diegoaguilar

Nigdy nie można było przypisać INSTANCEodwołania się do innego obiektu. Nie ma znaczenia, skąd pochodzi inny obiekt. (Uwaga: jest jeden INSTANCEna ClassLoader, który załadował tę klasę. Teoretycznie możesz załadować klasę kilka razy w jednym JVM i każda jest oddzielna. Ale to inny, techniczny punkt.)
Sean Owen

@AkhilGite twoja zmiana w mojej odpowiedzi sprawiła, że ​​była błędna; faktycznie odwróciło znaczenie zdania, które było poprawne. Odniesienie jest niezmienne. Obiekt pozostaje zmienny. Nie „staje się niezmienna”.
Sean Owen

@SeanOwen Sry za edycję, którą zrobiłem, Twoje oświadczenie jest całkowicie poprawne i dzięki.
AkhilGite

33

Bycie ostatecznym to nie to samo, co bycie niezmiennym.

final != immutable

Słowo finalkluczowe jest używane, aby upewnić się, że odniesienie nie zostanie zmienione (to znaczy, że odniesienie, które ma, nie może zostać zastąpione nowym)

Ale jeśli atrybut self jest modyfikowalny, możesz zrobić to, co właśnie opisałeś.

Na przykład

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

Jeśli utworzymy instancję tej klasy, nie będziemy mogli przypisać innej wartości do atrybutu, someFinalObjectponieważ tak jest ostateczny .

Więc to nie jest możliwe:

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

Ale jeśli sam obiekt jest zmienny w następujący sposób:

class MutableObject {
     private int n = 0;

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

Następnie wartość zawarta w tym zmiennym obiekcie może zostać zmieniona.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Ma to taki sam efekt, jak Twój post:

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

Tutaj nie zmieniasz wartości INSTANCE, tylko modyfikujesz jej stan wewnętrzny (metoda, provider.add)

jeśli chcesz zapobiec zmianie definicji klasy w ten sposób:

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

Ale to może nie mieć większego sensu :)

Nawiasem mówiąc, musisz również zsynchronizować dostęp do niego w zasadzie z tego samego powodu.


26

Klucz do nieporozumienia znajduje się w tytule pytania. To nie obiekt jest ostateczny, to zmienna . Wartość zmiennej nie może się zmienić, ale zawarte w niej dane mogą.

Zawsze pamiętaj, że kiedy deklarujesz zmienną typu referencyjnego, wartość tej zmiennej jest referencją, a nie obiektem.


11

Final oznacza tylko, że odniesienia nie można zmienić. Nie możesz ponownie przypisać INSTANCE do innego odwołania, jeśli jest ono zadeklarowane jako ostateczne. Stan wewnętrzny obiektu jest nadal zmienny.

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

zgłosiłby błąd kompilacji


7

Po przypisaniu finalzmiennej zawsze zawiera ona tę samą wartość. Jeśli finalzmienna zawiera odniesienie do obiektu, wówczas stan obiektu może zostać zmieniony przez operacje na obiekcie, ale zmienna będzie zawsze odnosić się do tego samego obiektu. Dotyczy to również tablic, ponieważ tablice są obiektami; jeśli finalzmienna zawiera odniesienie do tablicy, wówczas składniki tablicy mogą zostać zmienione przez operacje na tablicy, ale zmienna będzie zawsze odnosić się do tej samej tablicy.

Źródło

Oto przewodnik, jak uczynić obiekt niezmiennym .


4

Ostateczne i niezmienne to nie to samo. Ostatnia oznacza, że ​​odniesienia nie można ponownie przypisać, więc nie można tego powiedzieć

INSTANCE = ...

Niezmienny oznacza, że ​​sam obiekt nie może być modyfikowany. Przykładem tego jest java.lang.Stringklasa. Nie możesz modyfikować wartości ciągu.


2

Java nie ma koncepcji niezmienności wbudowanej w język. Nie ma możliwości oznaczenia metod jako mutatora. Dlatego język nie ma możliwości wymuszenia niezmienności obiektu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.