- vừa được xem lúc

Tại sao 1 == 1 lại đúng trong khi 1000 == 1000 thì sai với Integer wrappers trong Java?

0 0 5

Người đăng: Phát Nguyễn

Theo Viblo Asia

Hi forks!

Tuần này mình lại sml với bọn Java (unit test), thiết nghĩ chỉ viết mỗi unit test sẽ sớm bị "out trình" Java nên mình lân la trên Google, StackOverflow tìm vài món Java hack não để luyện tập, và mình vô tình lướt qua 1 câu hỏi thú vị (như tiêu đề). Bài viết này mình sẽ chia sẻ với bạn đọc những thứ hay ho của Java mà mình học được được sau khi giải ngố câu hỏi trên.

1. Đặt vấn đề

Để làm rõ vấn đề trong câu hỏi chính của bài viết, cùng mình xem qua đoạn code Java bên dưới

public static void main(String[] args) { Integer a = 1; Integer b = 1; Integer e = new Integer(1); Integer c = 1000; Integer d = 1000; System.out.println(a == b); System.out.println(a == e); System.out.println(c == d); }

Và kết quả sau khi chạy màn main

true
false
false

Để có thể giải thích kết quả trên, trước tiên cùng mình ôn lại một số kiến thức Java căn bản

2. So sánh Object trong Java

Để so sánh 2 objects trong Java, chắc hẳn chúng ta đều biết không nên sử dụng toán tử ==, toán tử này so sánh tham chiếu của 2 objects thay vì giá trị của chúng.

Tham chiếu là địa chỉ bộ nhớ mà tại đó các objects sẽ được lưu trữ

Ví dụ:

Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // false, compares their references
System.out.println(a.equals(b)); // true, compare their values

Đoạn code đầu bài sử dụng toán tử ==. Dangerous!!!

3. Wrapper classes & Auto boxing

Trong Java, chúng ta có 2 cách để thể hiện giá trị integer: sử dụng primitive types hoặc wrapper classes.

integer a = 100; // primitive types
Integer b = 100; // wrapper class: Integer

primitive types sẽ đơn giản là lưu giá trị một cách trực tiếp, như int, float, double,...

wrapper classes thì lại là Object trong Java, nó sẽ bọc giá trị của chúng ta trong 1 object, như Integer là object và bên trong sẽ là một giá trị int.

Một trong những lợi ích của wrapper classes mà mình hay trải nghiệm đó là "đẻ" ra NullPointerException 🤣 Đùa 1 tí, primitive types và wrapper classes sẽ giúp chúng ta giải quyết vấn đề "tham chiếu, tham trị" khi truyền đối số vào method (và nhiều lợi ích khác, các bạn có thể Google để tìm hiểu thêm).

Auto boxing là một cơ chế của Java compilier, compilier sẽ tự động convert primitive types sang wrapper class tương ứng. Ví dụ integer thành Integer, double thành Double, cách dùng auto boxing đơn giản là gán trực tiếp primitive types value cho wrapper classes

Integer a = 200;
// behind the scenes
// Integer a = Integer.valueOf(200);

Đến đây, chúng ta cùng quay lại đoạn code đầu bài, phân tích một chút

public static void main(String[] args) { Integer a = 1; // autoboxing Integer b = 1; // autoboxing Integer e = new Integer(1); // using new keyword, not autoboxing Integer c = 1000; // autoboxing Integer d = 1000; // autoboxing System.out.println(a == b); // compare their references System.out.println(a == e); // compare their references System.out.println(c == d); // compare their references }

Dựa vào kiến thức đầu bài đến giờ, chúng ta dễ dàng xác định được a == b, c == d đều cho kết quả FALSE, nhưng kết quả của chương trình hơi "cấn cấn" nhỉ. Đó là vì mình thiếu 1 kiến thức thú vị bên dưới.

4. Integer caching

"Object creation is expensive" bởi vậy nên Integer class có thứ gọi là integer caching, một kĩ thuật để optimize performance, giúp chúng ta tái sử dụng lại các Integer objects thay vì phải tạo mới object mỗi lần sử dụng. Trước tiên, cùng xem implement của method Integer.valueOf (method behind the scenes của autoboxing)

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

Sự xuất hiện của class IntegerCache và javaDoc đã thể hiện rõ ràng, Integer dùng class IntegerCache để cache lại các objects, cụ thể có range value từ -128 đến 127. Vì vậy, ab - 2 objects dùng autoboxing sẽ trỏ đến cùng 1 object (chứa value int 1) vì giá trị int của chúng nằm trong range value caching, do đó khi so sánh == cho ra giá trị TRUE.

Đối với e, do sử dụng new keyword, nó sẽ được tạo riêng 1 object mới và vì thế nên so sánh == với a sẽ cho ra FALSE, tương tự khi so sánh b == e

Chúng ta có thể dùng
-Djava.lang.Integer.IntegerCache.high=<size>
Hoặc
-XX:AutoBoxCacheMax=<size>
Để tăng giới hạn trên cho cache, ví dụ size = 1000, Integer sẽ cách object từ -127 đến 1000. Hiện tại chưa có config để tăng giới hạn dưới. 

5. Kết

Vậy là chúng ta vừa ôn lại một vài kiến thức thú vị trong Java, đặc biệt là kiến thức về so sánh 2 objects, chúng ta (luôn) nên dùng equals thay vì ==, trừ khi giá trị của chúng nằm trong vùng cache đối với Integer. Hiện tại Double, Float chưa có cơ chế cache này.

6. Tham khảo

Autoboxing: https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html

Integer caching: https://howtodoinjava.com/java-examples/internal-cache-wrapper-classes/

Hẹn gặp lại các bạn trong những bài sau. Cheerr!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 277

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 257

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 37

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 180

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 55

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 124