1. Giới thiệu
Trong Java tồn tại hai họ dữ liệu khác nhau. Thứ nhất — các kiểu nguyên thủy: int, char, double, boolean và các kiểu khác. Chúng đơn giản và nhanh: giá trị được lưu trực tiếp trong ô nhớ. Họ thứ hai — các kiểu tham chiếu, tức là đối tượng: biến lưu một tham chiếu tới nơi đối tượng nằm trong bộ nhớ.
Đôi khi cần dùng một kiểu nguyên thủy như một đối tượng. Nhưng kiểu nguyên thủy vốn dĩ không phải đối tượng. Trong những trường hợp như vậy, các lớp bao bọc chuyên dụng sẽ giúp ích.
Các kiểu bao bọc là các lớp lưu giữ bên trong giá trị của kiểu nguyên thủy và cho phép làm việc với nó như với đối tượng. Ví dụ, với int — lớp Integer, với double — Double, với char — Character, với boolean — Boolean. Các lớp này cung cấp các phương thức: Integer.parseInt(), Double.isInfinite(), Character.isLetter(), Boolean.parseBoolean() v.v.
Ví dụ lấy đối tượng Integer từ nguyên thủy int:
// Nguyên thủy
int a = 10;
// Đối tượng bao bọc
Integer b = Integer.valueOf(10);
Trong biến a lưu chính số 10. Còn biến b lưu một tham chiếu tới đối tượng, đối tượng này chứa 10 bên trong.
2. Vì sao cần wrapper
Kiểu nguyên thủy luôn là một giá trị cụ thể, không thể gán bằng null. Đối tượng có thể không tồn tại và mang giá trị rỗng — tiện khi cần biểu thị “không rõ” hoặc “không có”.
Kiểu nguyên thủy không có phương thức. Không thể gọi trên một số thứ như x.toString(). Wrapper cung cấp các khả năng đó: Integer.parseInt("123") chuyển chuỗi thành số, và có thể chuyển số thành chuỗi bằng toString().
Tóm lại: cần wrapper ở nơi yêu cầu đối tượng — khả năng lưu null, gọi phương thức, truyền giá trị vào API chỉ chấp nhận đối tượng (ví dụ, các collection như List<Integer>).
Danh sách đầy đủ các kiểu bao bọc
| Kiểu nguyên thủy | Lớp bao bọc |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Đóng gói (boxing) và mở gói (unboxing)
Khi bạn tự tạo đối tượng bao bọc, bạn lấy kiểu nguyên thủy và “đóng gói” nó vào trong đối tượng:
int primitive = 42;
Integer wrapper = Integer.valueOf(primitive); // boxing
Để lấy lại giá trị nguyên thủy — “unbox” — dùng, chẳng hạn, intValue():
Integer wrapper = Integer.valueOf(42);
int primitive = wrapper.intValue(); // unboxing
Tự động boxing (Autoboxing)
Trình biên dịch sẽ tự thêm lời gọi valueOf(), nếu bạn gán một nguyên thủy cho biến kiểu bao bọc:
int a = 10;
Integer b = a; // autoboxing (Integer.valueOf(a))
Tự động unboxing (Unboxing)
Tình huống ngược lại — khi đối tượng bao bọc được dùng ở nơi cần kiểu nguyên thủy:
Integer c = 20;
int d = c; // unboxing (c.intValue())
int a = 10; // nguyên thủy
Integer b = a; // autoboxing
Integer c = Integer.valueOf(20);
int d = c; // unboxing
4. Những cạm bẫy
So sánh wrapper. Toán tử == đối với đối tượng so sánh tham chiếu, không phải giá trị:
Integer m = 200;
Integer n = 200;
System.out.println(m == n); // false, vì đây là các đối tượng khác nhau
Java cache các wrapper của số trong khoảng từ -128 đến 127, vì vậy đôi khi == sẽ trả về true, còn đôi khi — false. Để so sánh giá trị, luôn dùng equals():
Integer x = 100;
Integer y = 100;
System.out.println(x == y); // true, trùng cache
System.out.println(x.equals(y)); // true, so sánh theo giá trị
NullPointerException khi unboxing. Nếu wrapper bằng null mà bạn cố unbox — bạn sẽ nhận ngoại lệ:
Integer value = null;
int primitive = value; // Lỗi! NullPointerException
Hiệu năng và bộ nhớ. Wrapper chậm hơn kiểu nguyên thủy và tốn nhiều bộ nhớ hơn. Ở các đoạn mã nóng và khi làm việc với mảng số lớn, hãy chọn kiểu nguyên thủy.
5. Ví dụ
Ví dụ với Integer và Double
Chuyển chuỗi thành số:
String text = "123";
int number = Integer.parseInt(text);
System.out.println(number); // 123
Kiểm tra các giá trị đặc biệt của số thực:
double d = 1.0 / 0;
System.out.println(Double.isInfinite(d)); // true
double nan = 0.0 / 0.0;
System.out.println(Double.isNaN(nan)); // true
Ví dụ với Character
char lưu một ký tự; wrapper Character cung cấp các phép kiểm tra tiện lợi:
char ch = 'A';
Character wrapper = ch; // autoboxing
System.out.println(Character.isLetter(ch)); // true
System.out.println(Character.isDigit(ch)); // false
System.out.println(Character.toLowerCase(ch)); // 'a'
Ví dụ với Boolean
Kiểu nguyên thủy boolean chỉ có true/false. Wrapper Boolean cho phép lưu cả null:
Boolean flag = null; // hợp lệ
flag = Boolean.TRUE; // hằng đặc biệt
System.out.println(flag); // true
Chuyển chuỗi thành giá trị logic:
String s1 = "true";
String s2 = "false";
boolean b1 = Boolean.parseBoolean(s1);
boolean b2 = Boolean.parseBoolean(s2);
System.out.println(b1); // true
System.out.println(b2); // false
GO TO FULL VERSION