Mengapa objek akhir dapat dimodifikasi?


89

Saya menemukan kode berikut dalam basis kode yang saya kerjakan:

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

    ...

INSTANCEdideklarasikan sebagai final. Mengapa objek bisa ditambahkan INSTANCE? Bukankah itu seharusnya membatalkan penggunaan final. (Tidak).

Saya berasumsi jawabannya ada hubungannya dengan petunjuk dan ingatan, tetapi saya ingin tahu dengan pasti.


Kesalahpahaman ini cukup sering muncul, belum tentu sebagai pertanyaan. Seringkali sebagai jawaban atau komentar.
Robin

5
Penjelasan sederhana dari JLS: "Jika variabel terakhir menyimpan referensi ke suatu objek, maka status objek dapat diubah oleh operasi pada objek, tetapi variabel akan selalu merujuk ke objek yang sama." Dokumentasi JLS
realPK

Jawaban:


161

finalcukup membuat referensi objek tidak bisa diubah. Objek yang ditunjukkannya tidak dapat diubah dengan melakukan ini. INSTANCEtidak dapat merujuk ke objek lain, tetapi objek yang diacunya dapat berubah status.


1
+1, Untuk lebih jelasnya silakan periksa java.sun.com/docs/books/jls/second_edition/html/… , bagian 4.5.4.
Abel Morelos

Jadi katakanlah saya deserialize suatu ConfigurationServiceobjek dan saya mencoba melakukan INSTANCE = deserializedConfigurationServicetidak akan diizinkan?
diegoaguilar

Anda tidak pernah bisa menetapkan INSTANCEuntuk merujuk ke objek lain. Tidak masalah darimana benda lain itu berasal. (NB ada satu INSTANCEper ClassLoaderyang telah memuat kelas ini. Anda secara teori dapat memuat kelas beberapa kali dalam satu JVM dan masing-masing terpisah. Tapi ini adalah poin teknis yang berbeda.)
Sean Owen

@AkhilGite hasil edit Anda pada jawaban saya membuatnya salah; itu sebenarnya membalikkan pengertian kalimat, yang sebenarnya. Referensi tersebut tidak dapat diubah. Objek tetap bisa berubah. Itu tidak "menjadi kekal".
Sean Owen

@SeanOwen Sry atas suntingan yang saya lakukan, Pernyataan Anda sepenuhnya benar dan Terima kasih.
AkhilGite

33

Menjadi final tidak sama dengan tidak berubah.

final != immutable

Kata finalkunci digunakan untuk memastikan referensi tidak berubah (yaitu, referensi yang dimilikinya tidak dapat diganti dengan yang baru)

Tetapi, jika atributnya adalah self dapat dimodifikasi, maka tidak masalah untuk melakukan apa yang baru saja Anda jelaskan.

Contohnya

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

Jika kita membuat instance kelas ini, kita tidak akan dapat menetapkan nilai lain ke atribut someFinalObjectkarena sudah final .

Jadi ini tidak mungkin:

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

Tetapi jika objek itu sendiri bisa berubah seperti ini:

class MutableObject {
     private int n = 0;

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

Kemudian, nilai yang terkandung oleh objek yang bisa berubah itu dapat diubah.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Ini memiliki efek yang sama dengan postingan Anda:

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

Di sini Anda tidak mengubah nilai INSTANCE, Anda mengubah status internalnya (melalui, metode provider.add)

jika Anda ingin mencegah definisi kelas harus diubah seperti ini:

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

Tapi, itu mungkin tidak masuk akal :)

Omong-omong, Anda juga harus menyinkronkan akses ke sana pada dasarnya untuk alasan yang sama.


26

Kunci kesalahpahaman ada di judul pertanyaan Anda. Bukan objek yang final, melainkan variabelnya . Nilai variabel tidak bisa berubah, tetapi data di dalamnya bisa.

Ingatlah selalu bahwa ketika Anda mendeklarasikan variabel tipe referensi, nilai variabel itu adalah referensi, bukan objek.


11

final hanya berarti referensi tidak dapat diubah. Anda tidak dapat menetapkan ulang INSTANCE ke referensi lain jika itu dinyatakan sebagai final. Keadaan internal objek masih bisa berubah.

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

akan memunculkan kesalahan kompilasi


7

Sekali finalvariabel telah ditetapkan, itu selalu berisi nilai yang sama. Jika finalvariabel menyimpan referensi ke suatu objek, maka status objek dapat diubah dengan operasi pada objek, tetapi variabel akan selalu merujuk ke objek yang sama. Ini berlaku juga untuk larik, karena larik adalah objek; Jika sebuah finalvariabel menyimpan referensi ke sebuah array, maka komponen dari array tersebut dapat diubah dengan operasi pada array tersebut, tetapi variabel tersebut akan selalu merujuk ke array yang sama.

Sumber

Berikut panduan untuk membuat objek tidak berubah .


4

Final dan kekal bukanlah hal yang sama. Final berarti referensi tidak dapat dialihkan sehingga Anda tidak dapat mengatakannya

INSTANCE = ...

Tidak dapat diubah berarti objek itu sendiri tidak dapat dimodifikasi. Contohnya adalah java.lang.Stringkelas. Anda tidak dapat mengubah nilai string.


2

Java tidak memiliki konsep kekekalan yang dibangun ke dalam bahasa. Tidak ada cara untuk menandai metode sebagai mutator. Oleh karena itu bahasa tidak memiliki cara untuk memaksakan ketetapan objek.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.