Quản lý giá trị Null trong Java bằng cách sử dụng Optional

NullPointerException (NPE) là một trong những ngoại lệ phổ biến nhất gây ra lỗi ứng dụng trong Java. Trước Java 8, các nhà phát triển thường phải viết các câu lệnh kiểm tra null lồng nhau phức tạp để tránh lỗi này, làm cho mã nguồn trở nên khó đọc và khó bảo trì. Java 8 đã giới thiệu lớp Optional<T> như một giải pháp chứa (container-object) có thể hoặc không chứa giá trị không-null. Cách tiếp cận này thúc đẩy phong cách lập trình hàm và giúp xử lý các giá trị thiếu vắng một cách tường minh hơn.

Để hình dung vấn đề, hãy xem xét một kịch bản truy xuất dữ liệu lồng nhau trước khi có Optional. Giả sử chúng ta cần lấy mã bưu chính từ địa chỉ của một nhân viên:

String zipCode = null;
if (employee != null) {
    Address address = employee.getAddress();
    if (address != null) {
        City city = address.getCity();
        if (city != null) {
            zipCode = city.getZipCode();
        }
    }
}

Kiến trúc trên rất dài dòng. Lớp Optional cung cấp các phương pháp tiện lợi để xử lý các trường hợp này một cách gãy gọn hơn.

Tạo đối tượng Optional

Bạn có thể tạo một phiên bản Optional theo nhiều cách. Nếu bạn muốn tạo một Optional rỗng hoàn toàn, hãy sử dụng phương thức tĩnh empty():

Optional<Employee> emptyOpt = Optional.empty();

Nếu bạn chắc chắn đối tượng không phải là null, hãy sử dụng of(). Việc truyền null cho phương thức này sẽ ngay lập tức ném ra NullPointerException:

Employee staff = new Employee();
Optional<Employee> opt = Optional.of(staff);

Trong trường hợp đối tượng có thể là null hoặc không, phương thức an toàn nhất để sử dụng là ofNullable():

Optional<Employee> opt = Optional.ofNullable(staff);

Truy xuất và kiểm tra giá trị

Để lấy giá trị ra khỏi Optional, bạn có thể dùng get(). Tuy nhiên, phương thức này tương tự như việc truy cập trực tiếp; nếu Optional rỗng, nó sẽ ném ra NoSuchElementException.

Thay vào đó, nên sử dụng isPresent() để kiểm tra hoặc ifPresent() để thực thi hành động nếu giá trị tồn tại:

Optional<Employee> staffOpt = Optional.ofNullable(getEmployee());

// Sử dụng ifPresent để xử lý nếu tồn tại
staffOpt.ifPresent(emp -> System.out.println("Tên: " + emp.getName()));

Trả về giá trị mặc định

Lớp Optional cung cấp hai phương pháp chính để trả về giá trị thay thế khi đối tượng bị null: orElse()orElseGet().

Phương thức orElse() nhận một giá trị mặc định làm tham số. Nó sẽ trả về giá trị này nếu Optional rỗng:

Employee defaultEmp = new Employee("Unknown");
Employee result = staffOpt.orElse(defaultEmp);

Tuy nhiên, cần lưu ý rằng đối tượng trong orElse() luôn được đánh giá (dù Optional có giá trị hay không). Nếu việc tạo đối tượng mặc định tốn kém tài nguyên (ví dụ: gọi cơ sở dữ liệu), hãy sử dụng orElseGet(). Phương thức này nhận một Supplier và chỉ thực thi khi cần thiết:

Employee result = staffOpt.orElseGet(() -> createNewEmployee());

Để thấy rõ sự khác biệt về hiệu năng, hãy xem xét ví dụ sau:

public class OptionalDemo {
    public static void main(String[] args) {
        Employee existingEmp = new Employee("John");
        Optional<Employee> opt = Optional.ofNullable(existingEmp);

        System.out.println("--- Bắt đầu orElse ---");
        Employee res1 = opt.orElse(createDefault());
        
        System.out.println("--- Bắt đầu orElseGet ---");
        Employee res2 = opt.orElseGet(() -> createDefault());
    }

    private static Employee createDefault() {
        System.out.println("Đang tạo nhân viên mặc định...");
        return new Employee("Default");
    }
}

Kết quả đầu ra sẽ cho thấy createDefault() được gọi ngay lập tức với orElse, nhưng không được gọi với orElseGet:

--- Bắt đầu orElse ---
Đang tạo nhân viên mặc định...
--- Bắt đầu orElseGet ---

Ném ngoại lệ khi giá trị trống

Nếu bạn muốn xử lý trường hợp null bằng một ngoại lệ cụ thể thay vì trả về giá trị mặc định, hãy sử dụng orElseThrow():

Employee result = staffOpt.orElseThrow(() -> new IllegalArgumentException("Nhân viên không tồn tại"));

Chuyển đổi giá trị với Map và FlatMap

Optional hỗ trợ các phép biến đổi dữ liệu mạnh mẽ thông qua map()flatMap().

Phương thức map() nhận một hàm (Function), áp dụng nó cho giá trị bên trong (nếu có) và trả về một Optional mới chứa kết quả:

String email = staffOpt
    .map(Employee::getEmail)
    .orElse("no-email@company.com");

Phương thức flatMap() được sử dụng khi hàm ánh xạ trả về một Optional khác, giúp tránh tình trạng lồng Optional<Optional<T>>. Giả sử phương thức getAddress() trả về Optional<Address>:

class Employee {
    public Optional<Address> getAddress() { ... }
}

String city = staffOpt
    .flatMap(Employee::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");

Lọc giá trị

Cuối cùng, phương thức filter() cho phép bạn từ chối giá trị dựa trên một điều kiện. Nếu giá trị không thỏa mãn Predicate, nó sẽ chuyển đổi Optional đang có thành rỗng:

Optional<Employee> validStaff = staffOpt.filter(emp -> emp.getSalary() > 1000);

if (validStaff.isPresent()) {
    System.out.println("Nhân viên hợp lệ với lương cao");
}

Thẻ: Java Optional Java8 NullPointerException FunctionalProgramming

Đăng vào ngày 13 tháng 6 lúc 23:25