[Java Backend Zero to Hello] BÀI 2.4: OPTIONAL
📚 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émNullPointerException.
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