Tại sao đối tượng cuối cùng có thể được sửa đổi?


89

Tôi đã xem qua mã sau trong cơ sở mã mà tôi đang làm việc:

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được khai báo là final. Tại sao các đối tượng có thể được thêm vào INSTANCE? Điều đó không làm mất hiệu lực của việc sử dụng cuối cùng. (Nó không).

Tôi giả sử câu trả lời phải làm gì đó với con trỏ và bộ nhớ nhưng tôi muốn biết chắc chắn.


Quan niệm sai lầm này xuất hiện khá thường xuyên, không nhất thiết phải là một câu hỏi. Thường là một câu trả lời hoặc bình luận.
Robin

5
Giải thích đơn giản từ JLS: "Nếu một biến cuối cùng giữ một tham chiếu đến một đối tượng, thì trạng thái của đối tượng có thể được thay đổi bằng các thao tác trên đối tượng, nhưng biến sẽ luôn tham chiếu đến cùng một đối tượng." Tài liệu JLS
realPK

Câu trả lời:


161

finalchỉ đơn giản là làm cho tham chiếu đối tượng không thể thay đổi. Đối tượng mà nó trỏ tới không thể thay đổi bằng cách thực hiện điều này. INSTANCEkhông bao giờ có thể tham chiếu đến một đối tượng khác, nhưng đối tượng mà nó đề cập đến có thể thay đổi trạng thái.


1
+1, Để biết thêm chi tiết, vui lòng kiểm tra java.sun.com/docs/books/jls/second_edition/html/… , phần 4.5.4.
Abel Morelos

Vì vậy, giả sử tôi deserialize một ConfigurationServiceđối tượng và tôi cố gắng thực hiện INSTANCE = deserializedConfigurationServicesẽ không được phép?
diegoaguilar

Bạn không bao giờ có thể gán INSTANCEđể tham chiếu đến một đối tượng khác. Không quan trọng vật thể kia đến từ đâu. (NB có một INSTANCEmỗi ClassLoaderđã nạp lớp này Bạn có thể tải trong lý thuyết lớp nhiều lần trong một JVM và mỗi tách biệt Nhưng đây là một khác nhau, quan điểm kỹ thuật...)
Sean Owen

@AkhilGite chỉnh sửa của bạn cho câu trả lời của tôi đã làm sai; nó thực sự đảo ngược ý nghĩa của câu, điều đó đúng. Tham chiếu là bất biến. Đối tượng vẫn có thể thay đổi. Nó không "trở thành bất biến".
Sean Owen

@SeanOwen Hãy thử chỉnh sửa mà tôi đã thực hiện, Tuyên bố của bạn là hoàn toàn đúng và Cảm ơn.
AkhilGite

33

Cuối cùng không giống như là bất biến.

final != immutable

Các finaltừ khóa được sử dụng để đảm bảo rằng các tài liệu tham khảo là không thay đổi (có nghĩa là, các tài liệu tham khảo nó đã không thể được thay thế bằng một cái mới)

Tuy nhiên, nếu thuộc tính là self có thể sửa đổi được thì bạn có thể thực hiện những gì bạn vừa mô tả.

Ví dụ

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

Nếu chúng ta khởi tạo lớp này, chúng ta sẽ không thể gán giá trị khác cho thuộc tính someFinalObjectvì nó là thuộc tính cuối cùng .

Vì vậy, điều này là không thể:

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

Nhưng nếu đối tượng tự nó có thể thay đổi như thế này:

class MutableObject {
     private int n = 0;

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

Sau đó, giá trị được chứa bởi đối tượng có thể thay đổi đó có thể bị thay đổi.

SomeHighLevelClass someObject = new SomeHighLevelClass();

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

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

Điều này có tác dụng tương tự như bài đăng của bạn:

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

Ở đây, bạn không thay đổi giá trị của INSTANCE, bạn đang sửa đổi trạng thái bên trong của nó (thông qua, phương thức provider.add)

nếu bạn muốn ngăn việc định nghĩa lớp phải được thay đổi như thế này:

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

Nhưng, nó có thể không có nhiều ý nghĩa :)

Nhân tiện, bạn cũng phải đồng bộ hóa quyền truy cập vào nó về cơ bản vì lý do tương tự.


26

Mấu chốt của sự hiểu lầm nằm ở tiêu đề câu hỏi của bạn. Nó không phải là đối tượng cuối cùng, nó là biến . Giá trị của biến không thể thay đổi, nhưng dữ liệu bên trong biến có thể thay đổi.

Luôn nhớ rằng khi bạn khai báo một biến kiểu tham chiếu, giá trị của biến đó là một tham chiếu, không phải một đối tượng.


11

cuối cùng chỉ có nghĩa là không thể thay đổi tham chiếu. Bạn không thể gán lại INSTANCE cho một tham chiếu khác nếu nó được khai báo là cuối cùng. Trạng thái bên trong của đối tượng vẫn có thể thay đổi.

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

sẽ ném ra một lỗi biên dịch


7

Khi một finalbiến đã được gán, nó luôn chứa cùng một giá trị. Nếu một finalbiến giữ một tham chiếu đến một đối tượng, thì trạng thái của đối tượng có thể được thay đổi bằng các thao tác trên đối tượng, nhưng biến sẽ luôn tham chiếu đến cùng một đối tượng. Điều này cũng áp dụng cho mảng, vì mảng là đối tượng; nếu một finalbiến giữ một tham chiếu đến một mảng, thì các thành phần của mảng có thể được thay đổi bằng các phép toán trên mảng, nhưng biến sẽ luôn tham chiếu đến cùng một mảng.

Nguồn

Đây là hướng dẫn về cách làm cho một đối tượng không thể thay đổi .


4

Cuối cùng và bất biến không giống nhau. Cuối cùng nghĩa là không thể gán lại tham chiếu nên bạn không thể nói

INSTANCE = ...

Bất biến có nghĩa là bản thân đối tượng không thể được sửa đổi. Một ví dụ về điều này là java.lang.Stringlớp học. Bạn không thể sửa đổi giá trị của một chuỗi.


2

Java không có khái niệm về tính bất biến được tích hợp trong ngôn ngữ này. Không có cách nào để đánh dấu các phương thức là một trình đột biến. Do đó, ngôn ngữ không có cách nào để thực thi tính bất biến của đối tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.