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ủyLớp Wrapper tương ứng
intInteger
doubleDouble
charCharacter
booleanBoolean
byteByte
shortShort
longLong
floatFloat

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à:

Byte wrapper: 10
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à:

Byte primitive: 10
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ị trong ArrayList: 10
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à:

Giá trị: 11
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.

Câu Nói Truyền Cảm Hứng

“Không ai sinh ra đã giỏi. Mọi thành công đều bắt đầu từ một bước nhỏ.” – Lao Tzu

Không Gian Tích Cực

“Chúc bạn một ngày mới đầy năng lượng và sự sáng tạo, luôn tiến về phía trước.”