Số nguyên là bất biến


100

Tôi biết điều này có lẽ rất ngu ngốc, nhưng nhiều nơi cho rằng lớp Integer trong Java là không thể thay đổi, nhưng đoạn mã sau:

Integer a=3;
Integer b=3;
a+=b;
System.out.println(a);

Thực hiện mà không gặp bất kỳ sự cố nào cho kết quả (mong đợi) 6. Vì vậy, giá trị của a đã thay đổi. Điều đó không có nghĩa là Số nguyên có thể thay đổi? Câu hỏi thứ hai và một chút thừa: "Các lớp bất biến không cần các hàm tạo sao chép". Bất cứ ai quan tâm để giải thích tại sao?


12
Lớp này là bất biến, nhưng tính năng autoboxing đang làm cho những điều thú vị xảy ra: stackoverflow.com/questions/3085332/…
wkl

Cảm ơn, quyền anh là từ khóa tôi cần google :)
K.Steff

7
Bạn đang nhầm lẫn giữa bất biến với giá trị cuối cùng hoặc giá trị không đổi.
Mã nhiệt tình vào

Câu trả lời:


94

Bất biến không có nghĩa là akhông bao giờ có thể bằng một giá trị khác. Ví dụ, Stringcũng là bất biến, nhưng tôi vẫn có thể làm điều này:

String str = "hello";
// str equals "hello"
str = str + "world";
// now str equals "helloworld"

strđã không bị thay đổi, đúng hơn strlà bây giờ là một đối tượng hoàn toàn mới được khởi tạo, giống như của bạn Integer. Vì vậy, giá trị của akhông thay đổi, nhưng nó được thay thế bằng một đối tượng hoàn toàn mới, tức là new Integer(6).


14
"Điều này là do str bây giờ là một đối tượng hoàn toàn mới được khởi tạo". Hay nói đúng hơn là str (a varibale) trỏ đến một đối tượng mới. Bản thân đối tượng không thể thay đổi, nhưng vì biến không phải là biến cuối cùng nên nó có thể trỏ đến một đối tượng khác.
Sandman

Có, nó trỏ đến một đối tượng khác được khởi tạo do kết quả của +=hoạt động.
Travis Webb

11
Nói một cách chính xác, nó không cần phải là một đối tượng mới . Quyền anh sử dụng Integer.valueOf(int)và phương pháp đó duy trì một bộ nhớ cache của Integercác đối tượng. Vì vậy, kết quả của +=trên một Integerbiến có thể là một đối tượng đã tồn tại trước đó (hoặc thậm chí nó có thể là cùng một đối tượng ... trong trường hợp của a += 0).
Stephen C

1
Tại sao JavaDoc for String nói rõ ràng là bất biến của nó, nhưng JavaDoc for Integer thì không? Sự khác biệt đó là lý do tại sao tôi đọc Câu hỏi này ...
cellepo

50

alà một "tham chiếu" đến một số Integer (3), tốc độ viết tắt của bạn a+=bthực sự có nghĩa là làm điều này:

a = new Integer(3 + 3)

Vì vậy, không, Số nguyên không thể thay đổi, nhưng các biến trỏ đến chúng là *.

* Có thể có các biến bất biến, các biến này được biểu thị bằng từ khóa final, có nghĩa là tham chiếu có thể không thay đổi.

final Integer a = 3;
final Integer b = 3;
a += b; // compile error, the variable `a` is immutable, too.

18

Bạn có thể xác định rằng đối tượng đã thay đổi bằng cách sử dụng System.identityHashCode()(Một cách tốt hơn là sử dụng đơn giản, ==tuy nhiên nó không rõ ràng là tham chiếu thay vì giá trị đã thay đổi)

Integer a = 3;
System.out.println("before a +=3; a="+a+" id="+Integer.toHexString(System.identityHashCode(a)));
a += 3;
System.out.println("after a +=3; a="+a+" id="+Integer.toHexString(System.identityHashCode(a)));

bản in

before a +=3; a=3 id=70f9f9d8
after a +=3; a=6 id=2b820dda

Bạn có thể thấy "id" cơ bản của đối tượng được ađề cập đến đã thay đổi.


System.identityHashCode () là một mẹo rất hay. Cảm ơn vì điều đó.
Ad Infinitum

10

Đối với câu hỏi ban đầu được hỏi,

Integer a=3;
Integer b=3;
a+=b;
System.out.println(a);

Số nguyên là không thay đổi, vì vậy những gì đã xảy ra ở trên là 'a' đã được thay đổi thành một tham chiếu mới của giá trị 6. Giá trị ban đầu 3 không còn tham chiếu trong bộ nhớ (nó chưa được thay đổi), vì vậy nó có thể được thu thập.

Nếu điều này xảy ra với một Chuỗi, nó sẽ giữ trong nhóm (trong không gian PermGen) trong khoảng thời gian lâu hơn so với Số nguyên như nó mong đợi có tham chiếu.


8

Có Số nguyên là bất biến.

A là một tham chiếu trỏ đến một đối tượng. Khi bạn chạy + = 3, điều đó sẽ gán lại A để tham chiếu đến một đối tượng Integer mới, với một giá trị khác.

Bạn không bao giờ sửa đổi đối tượng ban đầu, thay vì bạn trỏ tham chiếu đến một đối tượng khác.

Đọc về sự khác biệt giữa các đối tượng và tài liệu tham khảo tại đây .


Đơn giản và dễ dàng nói trong ngôn ngữ của giáo dân, với tất cả những giải thích phức tạp khác ra khỏi đó :)
Roshan Fernando

5

Bất biến không có nghĩa là bạn không thể thay đổi giá trị cho một biến. Nó chỉ có nghĩa là bất kỳ phép gán mới nào cũng tạo ra một đối tượng mới (gán cho nó một vị trí bộ nhớ mới) và sau đó giá trị được gán cho nó.

Để tự hiểu điều này, hãy thực hiện phép gán Số nguyên trong một vòng lặp (với số nguyên được khai báo bên ngoài vòng lặp) và xem xét các đối tượng trực tiếp trong bộ nhớ.

Lý do tại sao hàm tạo bản sao không cần thiết cho các đối tượng không thể thay đổi là lẽ thường đơn giản. Vì mỗi phép gán tạo ra một đối tượng mới, về mặt kỹ thuật, ngôn ngữ đã tạo một bản sao rồi, vì vậy bạn không phải tạo một bản sao khác.


2

"Các lớp bất biến không cần các hàm tạo sao chép". Bất cứ ai quan tâm để giải thích tại sao?

Lý do là hiếm khi có nhu cầu sao chép (hoặc thậm chí bất kỳ điểm nào trong việc sao chép) một thể hiện của một lớp bất biến. Bản sao của đối tượng nên "giống như" bản gốc, và nếu nó giống nhau thì không cần tạo nó.

Tuy nhiên, có một số giả định cơ bản:

  • Nó giả định rằng ứng dụng của bạn không có bất kỳ ý nghĩa nào đối với danh tính đối tượng của các thể hiện của lớp.

  • Nó giả định rằng lớp đã nạp chồng equalshashCodesao cho bản sao của một thể hiện sẽ "giống như" bản gốc ... theo các phương thức này.

Một trong hai hoặc cả hai giả định đó đều thể sai và điều đó có thể đảm bảo việc bổ sung hàm tạo bản sao.


1

Đây là cách tôi hiểu bất biến

int a=3;    
int b=a;
b=b+5;
System.out.println(a); //this returns 3
System.out.println(b); //this returns 8

Nếu int có thể thay đổi, "a" sẽ in ra 8 nhưng không phải vì nó là bất biến, đó là lý do tại sao nó là 3. Ví dụ của bạn chỉ là một phép gán mới.


0

Tôi có thể làm rõ rằng Số nguyên (và các tín ngưỡng khác của nó như Float, Short, v.v.) là bất biến bằng mã mẫu đơn giản:

Mã mẫu

public class Test{
    public static void main(String... args){
        Integer i = 100;
        StringBuilder sb = new StringBuilder("Hi");
        Test c = new Test();
        c.doInteger(i);
        c.doStringBuilder(sb);
        System.out.println(sb.append(i)); //Expected result if Integer is mutable is Hi there 1000
    }

    private void doInteger(Integer i){
        i=1000;
    }

    private void doStringBuilder(StringBuilder sb){
        sb.append(" there");
    }

}

Kết quả thực tế

Kết quả đến với anh ấy Hi There 100 thay vì kết quả mong đợi (trong trường hợp cả sb và i là các đối tượng có thể thay đổi) Hi There 1000

Điều này cho thấy đối tượng được tạo bởi i trong main không bị sửa đổi, trong khi sb được sửa đổi.

Vì vậy, StringBuilder đã chứng minh hành vi có thể thay đổi nhưng không phải là Số nguyên.

Vì vậy Integer là Immutable. Do đó đã được chứng minh

Một mã khác không chỉ có Số nguyên:

public class Test{
    public static void main(String... args){
        Integer i = 100;
        Test c = new Test();
        c.doInteger(i);
        System.out.println(i); //Expected result is 1000 in case Integer is mutable
    }

    private void doInteger(Integer i){
        i=1000;
    }


}

Bạn đang làm hai việc khác nhau - cố gắng gán lại số nguyên và gọi một phương thức trên trình xây dựng chuỗi. Nếu bạn làm vậy private void doStringBuilder(StringBuilder sb){ sb = new StringBuilder(); }thì sbkhông thay đổi.
MT0

Tôi đã thêm StringBuilder (có thể thay đổi) để chỉ ghép nối Integer với một đối tượng khác có thể thay đổi. Nếu bạn muốn, bạn có thể xóa tất cả mã liên quan đến StringBuilder và chỉ cần in ra để tôi thấy 100.
Ashutosh Nigam

Điều này không chứng minh tính bất biến - tất cả những gì bạn đang làm là băm lại ví dụ này chứng tỏ rằng Java sử dụng giá trị truyền (và các giá trị được truyền cho các đối tượng là con trỏ).
MT0

Hãy thửprivate void doInteger(Integer i){ System.out.println( i == 100 ); i=1000; System.out.println( i == 100 ); }
MT0

@ MT0 Khi bạn chuyển theo giá trị StringBuilder vẫn trỏ đến cùng một đối tượng nhưng Integer đang truyền bản sao mới không tham chiếu đến cùng một đối tượng. Nếu bạn in ra trong doInteger, bạn đang hiển thị bản sao được sở hữu bởi chức năng không phải là chức năng chính. Chúng ta muốn xem đối tượng do tôi trỏ trong main có giống nhau hay không. Hy vọng nó sẽ xóa các khái niệm :) Ngoài ra, phiên bản bất biến của StringBuilder là String. Hãy cho tôi biết nếu bạn muốn tôi chia sẻ một mẫu cho nó.
Ashutosh Nigam

-1
public static void main(String[] args) {
    // TODO Auto-generated method stub

    String s1="Hi";
    String s2=s1;

    s1="Bye";

    System.out.println(s2); //Hi  (if String was mutable output would be: Bye)
    System.out.println(s1); //Bye

    Integer i=1000;
    Integer i2=i;

    i=5000;

    System.out.println(i2); // 1000
    System.out.println(i); // 5000

    int j=1000;
    int j2=j;

    j=5000;

    System.out.println(j2); // 1000
    System.out.println(j); //  5000


    char c='a';
    char b=c;

    c='d';

    System.out.println(c); // d
    System.out.println(b); // a
}

Đầu ra là:

Hi Bye 1000 5000 1000 5000 d a

Vì vậy, char có thể thay đổi, String Integer và int là bất biến.


1
Câu trả lời này không cung cấp bất kỳ thông tin nào từ những người khác.
Giulio Caccin
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.