Tại sao 128 == 128 sai nhưng 127 == 127 là đúng khi so sánh các trình bao bọc Integer trong Java?


172
class D {
    public static void main(String args[]) {
        Integer b2=128;
        Integer b3=128;
        System.out.println(b2==b3);
    }
}

Đầu ra:

false

class D {
    public static void main(String args[]) {
        Integer b2=127;
        Integer b3=127;
        System.out.println(b2==b3);
    }
}

Đầu ra:

true

Lưu ý: Các số từ -128 đến 127 là đúng.


10
Bạn có thể tìm thấy bexhuff.com/2006/11/java-1-5-autoboxing-wackyness thông tin.
Dominic Rodger

1
Làm thế nào bạn có được điểm để đặt câu hỏi này? nó thực sự rất thú vị, nhưng người ta không bao giờ bắt gặp một thứ như thế "trong thế giới thực" ... hay?
Mare Infinitus

Câu trả lời:


217

Khi bạn biên dịch một số bằng chữ trong Java và gán nó cho một Integer (viết hoa I), trình biên dịch sẽ phát ra:

Integer b2 =Integer.valueOf(127)

Dòng mã này cũng được tạo khi bạn sử dụng hộp thư tự động.

valueOf được triển khai sao cho một số số nhất định được "gộp" và nó trả về cùng một thể hiện cho các giá trị nhỏ hơn 128.

Từ mã nguồn java 1.6, dòng 621:

public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}

Giá trị của highcó thể được cấu hình thành giá trị khác, với thuộc tính hệ thống.

-Djava.lang.Integer.IntegerCache.high = 999

Nếu bạn chạy chương trình của mình với thuộc tính hệ thống đó, nó sẽ xuất ra đúng!

Kết luận rõ ràng: không bao giờ dựa vào hai tham chiếu giống hệt nhau, luôn so sánh chúng với .equals()phương thức.

Vì vậy, b2.equals(b3)sẽ in đúng cho tất cả các giá trị logic bằng nhau của b2, b3.

Lưu ý rằng Integerbộ đệm không có ở đó vì lý do hiệu năng, mà là để tuân thủ JLS, phần 5.1.7 ; nhận dạng đối tượng phải được cung cấp cho các giá trị -128 đến 127.

Số nguyên # valueOf (int) cũng ghi lại hành vi này:

phương pháp này có khả năng mang lại hiệu suất không gian và thời gian tốt hơn đáng kể bằng cách lưu trữ các giá trị được yêu cầu thường xuyên. Phương pháp này sẽ luôn lưu trữ các giá trị trong phạm vi -128 đến 127, bao gồm và có thể lưu trữ các giá trị khác ngoài phạm vi này.


1
lưu ý rằng các giá trị nhỏ hơn 127 sẽ bị java bỏ qua và các giá trị lớn hơn Integer.MAX_VALUE-128 sẽ được giới hạn.
Andreas Petersson

Các số nguyên được lưu trữ cho các giá trị byte trong Java 5 trở lên, tạo ra số nguyên mới (1) == số nguyên mới (1). Tuy nhiên, đây không phải là trường hợp trong Java 1.4 hoặc thấp hơn, vì vậy hãy cẩn thận nếu cuối cùng bạn phải hạ cấp xuống môi trường đó.
MetroidFan2002

11
không, điều này là sai số nguyên mới (1) == số nguyên mới (1) là sai bất kể jvm. AFAIK không có trình biên dịch sẽ gian lận tại từ khóa "mới". nó PHẢI luôn khởi tạo một đối tượng mới.
Andreas Petersson

1
@Holger điểm thú vị. Nhưng về mặt kỹ thuật có thể thay thế lớp Integer từ JDK bằng một hàm tùy chỉnh ... (đừng hỏi tại sao ai đó sẽ điên rồ như vậy) - thì nó có thể có tác dụng phụ không được phép tối ưu hóa
Andreas Petersson

1
@AndreasPeterson chắc chắn. Trình biên dịch phần cứng có nghĩa là trình biên dịch JIT, biết chính xác lớp triển khai thực tế và chỉ có thể tối ưu hóa, nếu hàm tạo không có tác dụng phụ. Hoặc tối ưu hóa biểu thức để chỉ tái tạo các tác dụng phụ, tiếp theo là sử dụng false. Trên thực tế, điều này có thể đã xảy ra ngày hôm nay, như là một tác dụng phụ của việc áp dụng Phân tích Thoát và Thay thế vô hướng.
Holger

24

Autoboxing cache -128 đến 127. Điều này được chỉ định trong JLS ( 5.1.7 ).

Nếu giá trị p được đóng hộp là true, false, byte, char trong phạm vi \ u0000 đến \ u007f hoặc số int hoặc short trong khoảng từ -128 đến 127, thì hãy để r1 và r2 là kết quả của bất kỳ hai chuyển đổi quyền anh nào của p. Luôn luôn là trường hợp r1 == r2.

Một quy tắc đơn giản cần nhớ khi giao dịch với các đối tượng là - sử dụng .equalsnếu bạn muốn kiểm tra xem hai đối tượng có "bằng nhau" không, sử dụng ==khi bạn muốn xem liệu chúng có trỏ đến cùng một thể hiện hay không.


1
Lưu ý: JLS đã thay đổi trong Java 9. Điều này hiện chỉ được đảm bảo cho các biểu thức hằng số thời gian biên dịch ; xem cập nhật để trả lời được chấp nhận.
Stephen C

9

Sử dụng các kiểu dữ liệu nguyên thủy, ints, sẽ tạo ra true trong cả hai trường hợp, đầu ra dự kiến.

Tuy nhiên, vì bạn đang sử dụng các đối tượng Integer, toán tử == có ý nghĩa khác.

Trong ngữ cảnh của các đối tượng, == kiểm tra xem các biến có tham chiếu đến cùng một tham chiếu đối tượng không.

Để so sánh giá trị của các đối tượng, bạn nên sử dụng phương thức equals () Eg

 b2.equals(b1)

sẽ cho biết b2 nhỏ hơn b1, lớn hơn hay bằng (kiểm tra API để biết chi tiết)


7

Đó là tối ưu hóa bộ nhớ trong Java liên quan.

Để tiết kiệm bộ nhớ, Java 'sử dụng lại' tất cả các đối tượng trình bao bọc có giá trị nằm trong các phạm vi sau:

Tất cả các giá trị Boolean (đúng và sai)

Tất cả giá trị Byte

Tất cả các giá trị Ký tự từ \ u0000 đến \ u007f (tức là 0 đến 127 ở dạng thập phân)

Tất cả các giá trị Short và Integer từ -128 đến 127.


3

Hãy xem Integer.java, nếu giá trị nằm trong khoảng từ -128 đến 127, thì nó sẽ sử dụng nhóm được lưu trong bộ nhớ cache, vì vậy (Integer) 1 == (Integer) 1trong khi(Integer) 222 != (Integer) 222

 /**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}       

0

Các câu trả lời khác mô tả lý do tại sao các hiệu ứng quan sát có thể được quan sát, nhưng đó thực sự là điểm quan trọng đối với các lập trình viên (thú vị, chắc chắn, nhưng một cái gì đó bạn nên quên tất cả khi viết mã thực tế).

Để so sánh các đối tượng Integer cho đẳng thức, sử dụng equalsphương thức.

Không tìm cách so sánh các đối tượng Integer cho đẳng thức bằng cách sử dụng toán tử nhận dạng , ==.

Có thể xảy ra rằng một số giá trị bằng nhau là các đối tượng giống hệt nhau, nhưng đây không phải là thứ thường được dựa vào.


-4

Tôi đã viết như sau vì vấn đề này không chỉ dành riêng cho Integer. Kết luận của tôi là thường xuyên hơn không nếu bạn sử dụng API không chính xác, bạn sẽ thấy hành vi không chính xác. Sử dụng chính xác và bạn sẽ thấy hành vi chính xác:

public static void main (String[] args) {
    Byte b1=127;
    Byte b2=127;

    Short s1=127; //incorrect should use Byte
    Short s2=127; //incorrect should use Byte
    Short s3=128;
    Short s4=128;

    Integer i1=127; //incorrect should use Byte
    Integer i2=127; //incorrect should use Byte
    Integer i3=128;
    Integer i4=128;

    Integer i5=32767; //incorrect should use Short
    Integer i6=32767; //incorrect should use Short

    Long l1=127L;           //incorrect should use Byte
    Long l2=127L;           //incorrect should use Byte
    Long l3=13267L;         //incorrect should use Short
    Long l4=32767L;         //incorrect should use Short
    Long l5=2147483647L;    //incorrect should use Integer 
    Long l6=2147483647L;    //incorrect should use Integer
    Long l7=2147483648L;
    Long l8=2147483648L;

    System.out.print(b1==b2); //true  (incorrect) Used API correctly
    System.out.print(s1==s2); //true  (incorrect) Used API incorrectly
    System.out.print(i1==i2); //true  (incorrect) Used API incorrectly
    System.out.print(l1==l2); //true  (incorrect) Used API incorrectly

    System.out.print(s3==s4); //false (correct) Used API correctly
    System.out.print(i3==i4); //false (correct) Used API correctly
    System.out.print(i5==i6); //false (correct) Used API correctly
    System.out.print(l3==l4); //false (correct) Used API correctly
    System.out.print(l7==l8); //false (correct) Used API correctly
    System.out.print(l5==l6); //false (correct) Used API incorrectly

}
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.