0

[Java Backend Zero to Hello] BÀI 2.4: OPTIONAL

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 2: Java Core Nâng Cao | Bài 19/86


BÀI 2.4: OPTIONAL

Mục tiêu

  • Hiểu vấn đề của null
  • Sử dụng Optional thay thế null
  • Áp dụng các method của Optional
  • Best practices

1. VẤN ĐỀ CỦA NULL

Null gây ra nhiều lỗi, đặc biệt là NullPointerException.

public String getUserEmail(Long userId) {
    User user = userRepository.findById(userId);  // Có thể null
    return user.getEmail();  // ❌ NullPointerException nếu user null
}

2. OPTIONAL LÀ GÌ?

Optional<T> là container có thể chứa hoặc không chứa giá trị non-null.

public Optional<String> getUserEmail(Long userId) {
    User user = userRepository.findById(userId);
    return Optional.ofNullable(user).map(User::getEmail);
}

3. TẠO OPTIONAL

// Optional có giá trị - KHÔNG ĐƯỢC null
Optional<String> opt1 = Optional.of("Hello");

// Optional có thể null
Optional<String> opt2 = Optional.ofNullable(getValue());

// Optional rỗng
Optional<String> opt3 = Optional.empty();

⚠️ Optional.of(null) sẽ ném NullPointerException.


4. KIỂM TRA VÀ LẤY GIÁ TRỊ

4.1 isPresent, isEmpty

Optional<String> opt = Optional.of("Hello");

if (opt.isPresent()) {
    System.out.println(opt.get());
}

// Java 11+
if (opt.isEmpty()) {
    System.out.println("Rỗng");
}

4.2 get() - Lấy giá trị (cẩn thận)

String value = opt.get();  // ❌ Có thể NoSuchElementException nếu rỗng

4.3 orElse - Giá trị mặc định

String value = opt.orElse("Default");

4.4 orElseGet - Giá trị mặc định (lazy)

String value = opt.orElseGet(() -> {
    // Tính toán phức tạp
    return computeDefault();
});

4.5 orElseThrow - Ném exception

String value = opt.orElseThrow();  // NoSuchElementException
String value2 = opt.orElseThrow(() -> new CustomException("Not found"));

4.6 or - Giá trị thay thế (Java 9+)

String value = opt.or(() -> Optional.of("Alternative"));

5. BIẾN ĐỔI

5.1 map - Biến đổi giá trị

Optional<User> userOpt = Optional.of(new User("An", 25));

Optional<String> name = userOpt.map(User::getName);
Optional<Integer> age = userOpt.map(User::getAge);

5.2 flatMap - Tránh Optional lồng nhau

Optional<User> userOpt = Optional.of(new User("An", 25));

// map trả về Optional<Optional<String>>
Optional<Optional<String>> nested = userOpt.map(u -> Optional.ofNullable(u.getEmail()));

// flatMap trả về Optional<String>
Optional<String> email = userOpt.flatMap(u -> Optional.ofNullable(u.getEmail()));

5.3 filter - Lọc

Optional<User> userOpt = Optional.of(new User("An", 25));

Optional<User> adult = userOpt.filter(u -> u.getAge() >= 18);

6. HÀNH ĐỘNG

6.1 ifPresent - Thực hiện nếu có giá trị

opt.ifPresent(value -> System.out.println(value));
opt.ifPresent(System.out::println);

6.2 ifPresentOrElse (Java 9+)

opt.ifPresentOrElse(
    value -> System.out.println("Có: " + value),
    () -> System.out.println("Rỗng")
);

7. STREAM VỚI OPTIONAL

// Lấy giá trị từ Stream
List<String> emails = users.stream()
    .map(User::getEmail)        // Optional<String>
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

// flatMap với Optional (Java 9+)
List<String> emails = users.stream()
    .map(User::getEmailOptional)
    .flatMap(Optional::stream)
    .collect(Collectors.toList());

8. OPTIONAL TRONG SPRING

Spring Data trả về Optional cho findById:

Optional<User> user = userRepository.findById(id);

user.ifPresent(u -> System.out.println(u.getName()));
String name = user.map(User::getName).orElse("Unknown");

9. BEST PRACTICES

9.1 ✅ NÊN

// Dùng Optional cho return type
public Optional<User> findById(Long id) { ... }

// Dùng orElse/orElseThrow
String name = opt.orElse("Default");
String name2 = opt.orElseThrow(() -> new NotFoundException());

// Dùng map/filter
Optional<String> email = userOpt.map(User::getEmail);

9.2 ❌ KHÔNG NÊN

// Không dùng Optional cho field
public class User {
    private Optional<String> email;  // ❌
}

// Không dùng Optional làm parameter
public void setUser(Optional<User> user) { ... }  // ❌

// Không gọi isPresent() rồi get()
if (opt.isPresent()) {
    return opt.get();  // ❌ Dùng orElse thay thế
}

// Không serialize Optional
// Optional không implement Serializable

10. BÀI TẬP THỰC HÀNH

Bài 1: User Service

public class UserService {
    private Map<Long, User> users = new HashMap<>();

    public Optional<User> findById(Long id) {
        return Optional.ofNullable(users.get(id));
    }

    public String getUserName(Long id) {
        return findById(id)
            .map(User::getName)
            .orElse("Unknown");
    }

    public String getUserEmailOrDefault(Long id) {
        return findById(id)
            .flatMap(User::getEmailOptional)
            .orElse("no-email@example.com");
    }
}

Bài 2: Chain Optional

public Optional<String> getUserShippingCity(Long userId) {
    return userRepository.findById(userId)
        .flatMap(User::getAddressOptional)
        .flatMap(Address::getCityOptional)
        .map(City::getName);
}

Bài 3: Validate với Optional

public Optional<String> validateEmail(String email) {
    if (email == null || email.isEmpty()) {
        return Optional.empty();
    }
    if (!email.contains("@")) {
        return Optional.empty();
    }
    return Optional.of(email);
}

11. TÓM TẮT

Method Mô tả
of(value) Tạo Optional, value không null
ofNullable(value) Tạo Optional, value có thể null
empty() Optional rỗng
isPresent() Có giá trị?
isEmpty() Rỗng? (Java 11+)
get() Lấy giá trị (cẩn thận)
orElse(default) Giá trị mặc định
orElseGet(supplier) Giá trị mặc định (lazy)
orElseThrow() Ném exception
map(function) Biến đổi
flatMap(function) Biến đổi, tránh nested
filter(predicate) Lọc
ifPresent(consumer) Thực hiện nếu có
ifPresentOrElse(consumer, runnable) Có/không thì làm gì

Bài tiếp theo: 2.5 Date/Time API


🧭 Điều Hướng Series

⬅️ Bài trước: BÀI 2.3: STREAM API

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 2.5: DATE/TIME API (Java 8+)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí