Quyền anh số nguyên kỳ lạ trong Java


114

Tôi vừa thấy mã tương tự như thế này:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

Khi chạy, khối mã này sẽ in ra:

false
true

Tôi hiểu lý do tại sao đầu tiên là false: bởi vì hai đối tượng là các đối tượng riêng biệt, vì vậy ==so sánh các tham chiếu. Nhưng tôi không thể tìm ra, tại sao câu lệnh thứ hai lại quay trở lại true? Có một số quy tắc autoboxing kỳ lạ bắt đầu khi giá trị của một Số nguyên nằm trong một phạm vi nhất định không? Những gì đang xảy ra ở đây?


1
Trông giống như một người bị mắc mưu của stackoverflow.com/questions/1514910/...

3
@RC - Không hoàn toàn là một bản dupe, nhưng một tình huống tương tự đã được thảo luận. Cảm ơn vì đã tham khảo.
Joel

2
điều này thật kinh khủng. đây là lý do tại sao tôi không bao giờ hiểu được điểm của toàn bộ đối tượng nguyên thủy, nhưng cả hai, nhưng được đóng hộp tự động, nhưng phụ thuộc, nhưng aaaaaaaaargh.
njzk2

1
@Razib: Từ "autoboxing" không phải là mã, vì vậy đừng định dạng nó như vậy.
Tom

Câu trả lời:


102

Các trueđơn hàng thực sự được bảo đảm bởi các đặc điểm kỹ thuật ngôn ngữ. Từ phần 5.1.7 :

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

Cuộc thảo luận tiếp tục, cho thấy rằng mặc dù dòng đầu ra thứ hai của bạn được đảm bảo, nhưng dòng đầu tiên thì không (xem đoạn cuối được trích dẫn bên dưới):

Lý tưởng nhất, quyền anh một giá trị nguyên thủy nhất định p, sẽ luôn mang lại một tham chiếu giống hệt nhau. Trong thực tế, điều này có thể không khả thi nếu sử dụng các kỹ thuật triển khai hiện có. Các quy tắc trên là một thỏa hiệp thực dụng. Mệnh đề cuối cùng ở trên yêu cầu các giá trị chung nhất định luôn được đóng hộp thành các đối tượng không thể phân biệt được. Việc triển khai có thể lưu vào bộ nhớ cache này, lười biếng hoặc háo hức.

Đối với các giá trị khác, công thức này không cho phép mọi giả định về danh tính của các giá trị được đóng hộp trên phần của lập trình viên. Điều này sẽ cho phép (nhưng không yêu cầu) chia sẻ một số hoặc tất cả các tài liệu tham khảo này.

Điều này đảm bảo rằng trong hầu hết các trường hợp phổ biến, hành vi sẽ là hành vi mong muốn, mà không áp dụng hình phạt hiệu suất quá mức, đặc biệt là trên các thiết bị nhỏ. Ví dụ: các triển khai hạn chế bộ nhớ hơn có thể lưu vào bộ nhớ cache tất cả các ký tự và quần short cũng như các số nguyên và độ dài trong phạm vi -32K - + 32K.


17
Cũng có thể cần lưu ý rằng autoboxing thực chất chỉ là cú pháp để gọi valueOfphương thức của lớp box (như Integer.valueOf(int)). Điều thú vị là JLS xác định chính xác việc giải mã mở hộp - bằng cách sử dụng intValue()et al - nhưng không phải là cách giải mã quyền anh.
gustafc

@gustafc không có cách nào khác để mở hộp Integerngoài thông qua publicAPI chính thức , tức là gọi intValue(). Nhưng có những cách khả thi khác để lấy một Integerthể hiện cho một intgiá trị, ví dụ như một trình biên dịch có thể tạo mã lưu giữ và sử dụng lại các Integerthể hiện đã tạo trước đó .
Holger

31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

Đầu ra:

false
true

Đúng, đầu ra đầu tiên được tạo ra để so sánh tham chiếu; 'a' và 'b' - đây là hai tham chiếu khác nhau. Trong điểm 1, thực sự có hai tham chiếu được tạo tương tự như:

Integer a = new Integer(1000);
Integer b = new Integer(1000);

Đầu ra thứ hai được tạo ra do JVMcố gắng tiết kiệm bộ nhớ, khi giá trị Integernằm trong phạm vi (từ -128 đến 127). Tại điểm 2, không có tham chiếu mới nào của kiểu Số nguyên được tạo cho 'd'. Thay vì tạo một đối tượng mới cho biến tham chiếu kiểu Số nguyên 'd', nó chỉ được gán với đối tượng đã tạo trước đó được tham chiếu bởi 'c'. Tất cả những điều này được thực hiện bởi JVM.

Các quy tắc tiết kiệm bộ nhớ này không chỉ dành cho Số nguyên. vì mục đích tiết kiệm bộ nhớ, hai trường hợp của các đối tượng trình bao bọc sau (trong khi được tạo thông qua quyền anh), sẽ luôn là == trong đó các giá trị ban đầu của chúng giống nhau -

  • Boolean
  • Byte
  • Ký tự từ \ u0000 đến \u007f(7f là 127 trong hệ thập phân)
  • Ngắn và Số nguyên từ -128 đến 127

2
Longcũng có bộ nhớ cache với cùng một phạm vi như Integer.
Eric Wang

8

Các đối tượng số nguyên trong một số phạm vi (tôi nghĩ có thể từ -128 đến 127) được lưu vào bộ nhớ đệm và sử dụng lại. Các số nguyên bên ngoài phạm vi đó nhận được một đối tượng mới mỗi lần.


1
Phạm vi này có thể được mở rộng bằng cách sử dụng thuộc java.lang.Integer.IntegerCache.hightính. Điều thú vị là Long không có tùy chọn đó.
Aleksandr Kravets,

5

Có, có một quy tắc tự động đóng hộp kỳ lạ bắt đầu khi các giá trị nằm trong một phạm vi nhất định. Khi bạn gán một hằng số cho một biến Đối tượng, không có gì trong định nghĩa ngôn ngữ nói rằng một đối tượng mới phải được tạo. Nó có thể sử dụng lại một đối tượng hiện có từ bộ nhớ cache.

Trên thực tế, JVM thường sẽ lưu trữ một bộ nhớ cache các Số nguyên nhỏ cho mục đích này, cũng như các giá trị như Boolean.TRUE và Boolean.FALSE.


4

Tôi đoán là Java giữ một bộ nhớ cache các số nguyên nhỏ đã được 'đóng hộp' bởi vì chúng rất phổ biến và nó tiết kiệm rất nhiều thời gian để sử dụng lại một đối tượng hiện có hơn là tạo một đối tượng mới.


4

Đó là một điều thú vị. Trong cuốn sách Java hiệu quả đề nghị luôn luôn ghi đè bằng cho các lớp của riêng bạn. Ngoài ra, để kiểm tra sự bình đẳng cho hai trường hợp đối tượng của một lớp java luôn sử dụng phương thức bằng.

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

trả lại:

true
true

@Joel yêu cầu một chủ đề rất khác, không phải bình đẳng số nguyên mà là hành vi thời gian chạy của đối tượng.
Iliya Kuznetsov

3

Trong Java, quyền anh hoạt động trong phạm vi từ -128 đến 127 cho một Số nguyên. Khi bạn đang sử dụng các số trong phạm vi này, bạn có thể so sánh nó với toán tử ==. Đối với các đối tượng Integer bên ngoài phạm vi, bạn phải sử dụng bằng.


3

Việc gán trực tiếp một ký tự int cho một tham chiếu Số nguyên là một ví dụ về tự động đóng gói, trong đó giá trị ký tự thành mã chuyển đổi đối tượng được trình biên dịch xử lý.

Vì vậy, trong giai đoạn biên dịch trình biên dịch chuyển đổi Integer a = 1000, b = 1000;thành Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Vì vậy, nó Integer.valueOf()thực sự là phương thức cung cấp cho chúng ta các đối tượng số nguyên và nếu chúng ta nhìn vào mã nguồn của Integer.valueOf()phương thức, chúng ta có thể thấy rõ ràng phương thức lưu trữ các đối tượng số nguyên trong phạm vi -128 đến 127 (bao gồm).

/**
 *
 * 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) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

Vì vậy, thay vì tạo và trả về các đối tượng số nguyên mới, Integer.valueOf()phương thức này sẽ trả về các đối tượng Số nguyên từ bên trong IntegerCachenếu int nghĩa đen được truyền lớn hơn -128 và nhỏ hơn 127.

Java lưu trữ các đối tượng số nguyên này bởi vì phạm vi số nguyên này được sử dụng rất nhiều trong lập trình hàng ngày, gián tiếp tiết kiệm một số bộ nhớ.

Bộ nhớ đệm được khởi tạo trong lần sử dụng đầu tiên khi lớp được tải vào bộ nhớ do khối tĩnh. Phạm vi tối đa của bộ đệm có thể được kiểm soát bằng -XX:AutoBoxCacheMaxtùy chọn JVM.

Hành vi bộ nhớ đệm này không áp dụng cho các đối tượng Integer chỉ, tương tự như Integer.IntegerCache chúng tôi cũng có ByteCache, ShortCache, LongCache, CharacterCachecho Byte, Short, Long, Charactertương ứng.

Bạn có thể đọc thêm bài viết của tôi về Java Integer Cache - Tại sao Integer.valueOf (127) == Integer.valueOf (127) là True .


0

Trong Java 5, một tính năng mới đã được giới thiệu để tiết kiệm bộ nhớ và cải thiện hiệu suất cho các xử lý đối tượng kiểu Số nguyên. Các đối tượng số nguyên được lưu trong bộ nhớ cache nội bộ và được sử dụng lại thông qua cùng các đối tượng được tham chiếu.

  1. Điều này có thể áp dụng cho các giá trị Số nguyên trong phạm vi từ –127 đến +127 (giá trị Số nguyên tối đa).

  2. Bộ nhớ đệm Số nguyên này chỉ hoạt động trên autoboxing. Các đối tượng số nguyên sẽ không được lưu vào bộ nhớ đệm khi chúng được tạo bằng hàm tạo.

Để biết thêm chi tiết xin vui lòng đi qua Liên kết bên dưới:

Chi tiết bộ nhớ đệm số nguyên


0

Nếu chúng ta kiểm tra mã nguồn của Integerobeject, chúng ta sẽ tìm thấy nguồn của valueOfphương thức giống như sau:

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

có thể giải thích tại sao Integercác đối tượng, trong phạm vi từ -128 ( Integer.low) đến 127 ( Integer.high), lại là các đối tượng được tham chiếu giống nhau trong quá trình tự động đóng hộp. Và chúng ta có thể thấy có một lớp IntegerCachechăm sóc Integermảng bộ nhớ cache, đó là một lớp tĩnh bên trong riêng tư của Integerlớp.

Có một ví dụ thú vị khác có thể giúp chúng ta hiểu tình huống kỳ lạ này:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

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