Lớp Wrapper (Class Wrapper)
"Trong Java, lớp Wrapper là các lớp đặc biệt được sử dụng để bọc các kiểu dữ liệu nguyên thủy thành đối tượng. Điều này giúp các kiểu dữ liệu nguyên thủy có thể hoạt động như các đối tượng, hỗ trợ thao tác linh hoạt trong các cấu trúc dữ liệu và API. Dưới đây là những điều cơ bản bạn cần biết về lớp Wrapper trong Java."
Ⅰ. Lớp Wrapper là gì?
Lớp wrapper trong Java cung cấp cơ chế để chuyển đổi kiểu dữ liệu nguyên thủy (primitive data types) thành đối tượng và ngược lại.
Kể từ Java SE 5.0, tính năng autoboxing và unboxing tự động chuyển đổi giữa kiểu dữ liệu nguyên thủy và đối tượng. Việc tự động chuyển đổi từ kiểu dữ liệu nguyên thủy sang đối tượng được gọi là autoboxing và ngược lại là unboxing.
Sử dụng lớp Wrapper trong Java
↳ Java là ngôn ngữ lập trình hướng đối tượng, vì vậy chúng ta cần xử lý nhiều đối tượng trong các trường hợp như Collections (tập hợp), Serialization (chuỗi hóa), Synchronization (đồng bộ hóa), v.v. Hãy xem xét các trường hợp khác nhau mà chúng ta cần sử dụng lớp wrapper.
↳ Thay đổi giá trị trong phương thức: Java chỉ hỗ trợ truyền giá trị (call by value). Vì vậy, nếu chúng ta truyền một giá trị nguyên thủy, nó sẽ không thay đổi giá trị gốc. Nhưng nếu chúng ta chuyển đổi giá trị nguyên thủy thành một đối tượng, nó sẽ thay đổi giá trị gốc.
↳ Serialization: Chúng ta cần chuyển đổi các đối tượng thành luồng để thực hiện quá trình serialization. Nếu chúng ta có một giá trị nguyên thủy, chúng ta có thể chuyển đổi nó thành đối tượng thông qua các lớp wrapper.
↳ Synchronization: Đồng bộ hóa Java hoạt động với các đối tượng trong đa luồng.
↳ Gói java.util: Gói java.util cung cấp các lớp tiện ích để xử lý các đối tượng.
↳ Khung làm việc Collection: Khung làm việc collection của Java chỉ hoạt động với các đối tượng. Tất cả các lớp của khung làm việc collection (ArrayList, LinkedList, Vector, HashSet, LinkedHashSet, TreeSet, PriorityQueue, ArrayDeque, v.v.) chỉ xử lý các đối tượng.
Tại sao cần lớp Wrapper?
↳ Đưa kiểu nguyên thủy vào các cấu trúc dữ liệu: Các cấu trúc dữ liệu như List, Set, Map chỉ hoạt động với các đối tượng, không thể trực tiếp chứa các kiểu dữ liệu nguyên thủy.
↳ Sử dụng các phương thức của đối tượng: Các lớp wrapper cung cấp nhiều phương thức hữu ích như so sánh, chuyển đổi kiểu, v.v.
↳ Tạo các đối tượng không đổi (immutable): Các lớp wrapper thường là không đổi, giúp đảm bảo tính toàn vẹn của dữ liệu trong các môi trường đa luồng.
Có tám lớp trong gói java.lang được gọi là lớp wrapper trong Java. Danh sách tám lớp wrapper được liệt kê dưới đây:
Kiểu dữ liệu nguyên thủy | Lớp Wrapper tương ứng |
---|---|
int | Integer |
double | Double |
char | Character |
boolean | Boolean |
byte | Byte |
short | Short |
long | Long |
float | Float |
1. Autoboxing (Chuyển đổi từ kiểu nguyên thủy thành lớp Wrapper)
Tự động boxing (autoboxing) là quá trình Java tự động chuyển đổi một kiểu dữ liệu nguyên thủy thành lớp wrapper tương ứng khi cần thiết. Điều này giúp giảm bớt sự cần thiết phải chuyển đổi thủ công và làm cho mã nguồn trở nên gọn gàng hơn. Ví dụ: byte thành Byte, char thành Character, int thành Integer, long thành Long, float thành Float, boolean thành Boolean, double thành Double và short thành Short.
Kể từ Java 5, chúng ta không cần phải sử dụng phương thức valueOf() của các lớp wrapper để chuyển đổi kiểu dữ liệu nguyên thủy thành đối tượng.
Dưới đây là một ví dụ về autoboxing trong Java:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
// Kiểu nguyên thủy
byte primitiveByte = 10;
short primitiveShort = 20;
int primitiveInt = 30;
long primitiveLong = 40L;
float primitiveFloat = 50.0f;
double primitiveDouble = 60.0;
char primitiveChar = 'A';
boolean primitiveBoolean = true;
// Autoboxing: Chuyển đổi tự động thành lớp wrapper
Byte wrapperByte = primitiveByte;
Short wrapperShort = primitiveShort;
Integer wrapperInt = primitiveInt;
Long wrapperLong = primitiveLong;
Float wrapperFloat = primitiveFloat;
Double wrapperDouble = primitiveDouble;
Character wrapperChar = primitiveChar;
Boolean wrapperBoolean = primitiveBoolean;
// In ra kết quả
System.out.println("Byte wrapper: " + wrapperByte);
System.out.println("Short wrapper: " + wrapperShort);
System.out.println("Integer wrapper: " + wrapperInt);
System.out.println("Long wrapper: " + wrapperLong);
System.out.println("Float wrapper: " + wrapperFloat);
System.out.println("Double wrapper: " + wrapperDouble);
System.out.println("Character wrapper: " + wrapperChar);
System.out.println("Boolean wrapper: " + wrapperBoolean);
}
}
Kết quả của chương trình là:
Short wrapper: 20
Integer wrapper: 30
Long wrapper: 40
Float wrapper: 50.0
Double wrapper: 60.0
Character wrapper: A
Boolean wrapper: true
Autoboxing giúp chuyển đổi một cách tự động giữa kiểu dữ liệu nguyên thủy và lớp wrapper tương ứng, làm cho việc làm việc với các cấu trúc dữ liệu yêu cầu đối tượng (như ArrayList…) trở nên dễ dàng và tự nhiên hơn.
2. Unboxing (Chuyển đổi từ lớp wrapper thành kiểu nguyên thủy)
Unboxing là quá trình tự động chuyển đổi từ một lớp wrapper (như Integer, Double, Character, v.v.) về kiểu dữ liệu nguyên thủy tương ứng (như int, double, char, v.v.). Điều này cho phép bạn sử dụng các lớp wrapper một cách dễ dàng trong các phép toán và các cấu trúc dữ liệu yêu cầu kiểu nguyên thủy.
Dưới đây là một ví dụ về unboxing trong Java:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
// Lớp wrapper
Byte wrapperByte = new Byte((byte) 10);
Short wrapperShort = new Short((short) 20);
Integer wrapperInt = new Integer(30);
Long wrapperLong = new Long(40L);
Float wrapperFloat = new Float(50.0f);
Double wrapperDouble = new Double(60.0);
Character wrapperChar = new Character('A');
Boolean wrapperBoolean = new Boolean(true);
// Unboxing: Chuyển đổi tự động thành kiểu nguyên thủy
byte primitiveByte = wrapperByte;
short primitiveShort = wrapperShort;
int primitiveInt = wrapperInt;
long primitiveLong = wrapperLong;
float primitiveFloat = wrapperFloat;
double primitiveDouble = wrapperDouble;
char primitiveChar = wrapperChar;
boolean primitiveBoolean = wrapperBoolean;
// In ra kết quả
System.out.println("Byte primitive: " + primitiveByte);
System.out.println("Short primitive: " + primitiveShort);
System.out.println("Integer primitive: " + primitiveInt);
System.out.println("Long primitive: " + primitiveLong);
System.out.println("Float primitive: " + primitiveFloat);
System.out.println("Double primitive: " + primitiveDouble);
System.out.println("Character primitive: " + primitiveChar);
System.out.println("Boolean primitive: " + primitiveBoolean);
}
}
Kết quả của chương trình là:
Short primitive: 20
Integer primitive: 30
Long primitive: 40
Float primitive: 50.0
Double primitive: 60.0
Character primitive: A
Boolean primitive: true
Unboxing giúp làm việc với các lớp wrapper trở nên dễ dàng hơn bằng cách tự động chuyển đổi chúng thành kiểu dữ liệu nguyên thủy khi cần. Điều này giúp bạn có thể sử dụng các lớp wrapper trong các cấu trúc dữ liệu và phép toán mà yêu cầu kiểu dữ liệu nguyên thủy.
Dưới đây là một ví dụ về việc thêm các giá trị nguyên thủy vào các collection như List trong Java:
Ví dụ: Example.java
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
// Tạo một ArrayList chứa các đối tượng Integer
List<Integer> numbers = new ArrayList<>();
// Thêm kiểu dữ liệu nguyên thủy vào ArrayList
numbers.add(10); // Autoboxing xảy ra ở đây
// Lấy giá trị từ ArrayList và chuyển đổi trở lại kiểu dữ liệu nguyên thủy
int number = numbers.get(0); // Unboxing xảy ra ở đây
// In ra các giá trị
System.out.println("Giá trị trong ArrayList: " + numbers.get(0));
System.out.println("Giá trị số nguyên: " + number);
}
}
Kết quả của chương trình là:
Giá trị số nguyên: 10
Giải thích:
↳ Autoboxing: Trong dòng numbers.add(10);, bạn đang thêm một giá trị kiểu nguyên thủy int vào một ArrayList yêu cầu đối tượng Integer. Java tự động chuyển đổi int thành Integer qua cơ chế autoboxing.
↳ Unboxing: Trong dòng int number = numbers.get(0);, bạn đang lấy một giá trị kiểu Integer từ ArrayList và gán cho một biến kiểu int. Java tự động chuyển đổi Integer thành int qua cơ chế unboxing.
Kết Luận: Autoboxing giúp chuyển đổi một cách tự động giữa kiểu dữ liệu nguyên thủy và lớp wrapper tương ứng, làm cho việc làm việc với các cấu trúc dữ liệu yêu cầu đối tượng (như ArrayList) trở nên dễ dàng và tự nhiên hơn.
Ⅱ. Lớp Wrapper tùy chỉnh
Bạn có thể tạo lớp wrapper tùy chỉnh để đóng gói giá trị kiểu dữ liệu nguyên thủy theo cách tương tự như các lớp wrapper tích hợp sẵn (Integer, Double, Boolean, v.v.). Lớp wrapper tùy chỉnh thường được sử dụng khi bạn cần bổ sung hoặc thay đổi hành vi của một kiểu dữ liệu nguyên thủy hoặc khi bạn cần thêm chức năng cho loại dữ liệu mà Java không cung cấp sẵn.
Ví dụ: MyInteger.java
// Lớp Wrapper Tùy Chỉnh
class MyInteger {
private int value; // Giá trị nguyên thủy được bao bọc
// Constructor
public MyInteger(int value) {
this.value = value;
}
// Getter
public int getValue() {
return value;
}
// Setter
public void setValue(int value) {
this.value = value;
}
// Phương thức kiểm tra số chẵn
public boolean isEven() {
return value % 2 == 0;
}
// Phương thức kiểm tra số lẻ
public boolean isOdd() {
return value % 2 != 0;
}
// Override phương thức toString
@Override
public String toString() {
return Integer.toString(value);
}
// Override phương thức equals
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MyInteger that = (MyInteger) obj;
return value == that.value;
}
// Override phương thức hashCode
@Override
public int hashCode() {
return Integer.hashCode(value);
}
// Phương thức main để kiểm tra lớp wrapper tùy chỉnh
public static void main(String[] args) {
MyInteger myInt = new MyInteger(11);
System.out.println("Giá trị: " + myInt.getValue());
System.out.println("Số chẵn: " + myInt.isEven());
System.out.println("Số lẻ: " + myInt.isOdd());
System.out.println("Chuỗi: " + myInt.toString());
}
}
Kết quả của chương trình là:
Số chẵn: false
Số lẻ: true
Chuỗi: 11
Lớp wrapper tùy chỉnh cho phép bạn mở rộng và tùy chỉnh hành vi của các kiểu dữ liệu nguyên thủy theo nhu cầu cụ thể của ứng dụng của bạn. Điều này có thể hữu ích khi bạn cần thêm chức năng hoặc logic đặc biệt mà các lớp wrapper tích hợp sẵn không cung cấp.