Vấn đề mà Optional giải quyết
Trong Java, một phương thức có thể không có câu trả lời luôn phải đứng trước một lựa chọn khó xử: trả về null và hy vọng bên gọi nhớ kiểm tra. Thường thì họ không nhớ, và kết quả là một NullPointerException xuất hiện ở chỗ cách xa phương thức đã trả về null.
Optional<T> là một chiếc hộp nhỏ, hoặc chứa một giá trị, hoặc rỗng một cách tường minh. Khi trả về Optional<User> thay vì User, phương thức nói với bên gọi rằng "có thể không tìm thấy gì" ngay trong chữ ký của nó, và trình biên dịch hướng họ tới việc xử lý trường hợp đó.
Tạo một Optional
Có ba phương thức factory, và chọn đúng cái là điều quan trọng:
Optional.of(value): giá trị phải khác null, nếu không sẽ némNullPointerExceptionngay tại chỗ.Optional.ofNullable(value): rỗng nếu giá trị là null, có giá trị trong trường hợp ngược lại. Dùng nó để bọc thứ có thể là null.Optional.empty(): một Optional rỗng.
Lỗi hay gặp là dùng Optional.of(x) cho một giá trị có thể là null: làm vậy đi ngược mục đích và ném ra đúng ngoại lệ bạn đang cố tránh. Khi không chắc, hãy dùng ofNullable.
Đọc giá trị an toàn
Khi đã có một Optional, mục tiêu là lấy giá trị ra mà không giả định là nó có ở đó. Công cụ thô nhất là get(), và bạn gần như không bao giờ nên dùng nó:
Optional<String> name = Optional.empty();
String value = name.get(); // throws NoSuchElementException - just NPE in disguise
Thay vào đó, hãy cung cấp giá trị dự phòng hoặc phản ứng khi có giá trị. orElse trả về giá trị mặc định khi rỗng; ifPresent chỉ chạy code khi có giá trị:
Dùng orElseGet(supplier) thay cho orElse khi giá trị mặc định tốn công tạo ra: supplier chỉ chạy nếu Optional thực sự rỗng. Đối số của orElse thì luôn được tính, kể cả khi không cần đến.
orElseThrow cho trường hợp "lẽ ra phải có"
Đôi khi rỗng thật sự là lỗi, như một giá trị cấu hình bắt buộc hay một người dùng lẽ ra phải có trong cơ sở dữ liệu. orElseThrow biến một Optional rỗng thành một ngoại lệ rõ ràng do bạn chọn:
Cách này dễ đọc hơn nhiều so với một khối if (opt.isPresent()) và làm lỗi lộ rõ ngay tại chỗ nó xảy ra.
Biến đổi với map và filter
Lợi ích thật sự nằm ở việc nối chuỗi các lời gọi. map áp dụng một hàm lên giá trị chỉ khi có giá trị và giữ nguyên một Optional rỗng là rỗng, nhờ vậy bạn biến đổi dữ liệu mà không bao giờ đụng tới null. filter bỏ giá trị đi nếu nó không vượt qua một phép kiểm tra.
Nếu chính hàm ánh xạ của bạn trả về một Optional, hãy dùng flatMap thay cho map để tránh kiểu Optional<Optional<T>> khó dùng. Điều này giống với cách phân biệt map/flatMap mà bạn đã thấy ở stream: một Optional hoạt động khá giống một stream có không hoặc một phần tử.
Optional nên dùng ở đâu (và không nên dùng ở đâu)
Optional được thiết kế làm kiểu trả về cho những phương thức có thể hợp lệ mà không cho ra kết quả nào. Ví dụ tiêu biểu là Stream.findFirst(), các kiểu tra cứu giống Map và việc phân tích dữ liệu (parsing). Dùng nó ở những chỗ đó, API của bạn sẽ tự ghi rõ những chỗ có thể thiếu giá trị.
Nó không dành cho:
- Trường (field). Nó không serializable được và tốn thêm một đối tượng cho mỗi trường. Hãy dùng tham chiếu bình thường và kiểm tra trong hàm khởi tạo.
- Tham số phương thức. Khi đó bên gọi phải bọc đối số trong
Optional.of(...), rườm rà hơn so với một phiên bản nạp chồng hoặc một tham số có thể null. - Collection. Hãy trả về một
Listrỗng, không phảiOptional<List>. Một collection rỗng vốn đã có nghĩa là "không có gì".
Khi được dùng làm kiểu trả về, Optional biến những bug null âm thầm thành lời nhắc ngay lúc biên dịch để bạn xử lý trường hợp thiếu giá trị.
Tiếp theo: ngoại lệ
Optional xử lý gọn gàng trường hợp thường ngày "có thể không có giá trị", nhưng một số lỗi thật sự là bất thường: một tệp không mở được, mạng bị ngắt, dữ liệu đầu vào không phân tích được. Java mô hình hóa những tình huống đó bằng ngoại lệ (exception), và đó là nội dung của trang tiếp theo.
Câu hỏi thường gặp
Optional trong Java là gì và vì sao nên dùng?
Optional<T> là một kiểu chứa, hoặc giữ một giá trị, hoặc rỗng. Nó giúp ghi rõ khả năng "có thể không có giá trị" ngay trong kiểu trả về của phương thức, để bên gọi được nhắc xử lý trường hợp rỗng thay vì quên kiểm tra null rồi gặp NullPointerException lúc chạy. Hãy dùng nó chủ yếu làm kiểu trả về cho những phương thức có thể không cho ra kết quả, như một lần tra cứu không tìm thấy gì.
Optional.of và Optional.ofNullable khác nhau thế nào?
Optional.of(x) ném NullPointerException ngay lập tức nếu x là null: dùng nó khi bạn biết chắc giá trị khác null. Optional.ofNullable(x) trả về một Optional rỗng khi x là null và một Optional có giá trị trong trường hợp ngược lại: dùng nó khi giá trị có thể là null. Optional.empty() cho bạn trực tiếp một Optional rỗng.
Có nên gọi Optional.get() để đọc giá trị không?
Tránh gọi trực tiếp get(): nó ném NoSuchElementException nếu Optional rỗng, mà thực chất chỉ là NullPointerException mang tên khác. Hãy ưu tiên orElse, orElseGet, orElseThrow, ifPresent hoặc map để trường hợp rỗng luôn được xử lý. Nếu buộc phải kiểm tra trước, hãy bảo vệ bằng isPresent(), nhưng các phương thức theo phong cách hàm gọn gàng hơn.