Bản chất của Optional là một container (bao chứa). Nó có thể rỗng hoặc chứa giá trị NULL. Cấu trúc này nhắc nhở người dùng Optional object có thể không có gì bên trong nó để xử lý một cách thích hợp. Nó được giới thiệu trong Java 8 và nó khá giống với Optional trong Guava.
Java 8 - Tìm hiểu Optional Class và một số ví dụ

Các phương thức của Optional

Phương thức & mô tả
static <T> Optional<T> empty()
Trả về một Optional instance rỗng.
boolean equals(Object obj)
Sử dụng để so sánh với một đối tượng Optional khác và trả về true nếu bằng nhau hoặc false nếu ngược lại.
Optional<T> filter(Predicate<? super <T> predicate)
Nếu như có giá trị hiện diện trong đối tượng Optional này và giá trị của nó khớp với Predicate truyền vào, nó sẽ trả về một Optional chứa giá trị đó, mặc khác sẽ trả về một Optional rỗng.
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)
Nếu như có giá trị hiện diện trong Optional, nó sẽ áp dụng Function được truyền vào cho nó và trả về một Optional với kiểu U, ngược lại thì sẽ return về một empty Optional.
T get()
Nếu như có giá trị trong Optional này, nó sẽ trả về giá trị đó, ngược lại sẽ ném ra NoSuchElementException nếu như đối tượng rỗng.
int hashCode()
Trả về giá trị hash code nếu như có Optional đang chứa giá trị, ngược lại sẽ trả về 0 (zero) nếu như không có giá trị hiện diện trong Optional.
void ifPresent(Consumer<? super T> consumer)
Nếu như đối tượng Optional đang chứa giá trị, nó sẽ áp dụng consumer được truyền vào cho giá trị của nó. Ngược lại thì không làm gì cả.
boolean isPresent()
Phương thức này được sử dụng để kiểm tra xem trong đối tượng Optional có đang chứa giá trị hay không. Giá trị trả về là True nếu có giá trị và ngược lại trả về false.
<U>Optional<U> map(Function<? super T,? extends U> mapper)
Nếu như có giá trị nó sẽ áp dụng Function lên giá trị đó và nếu kết quả sau khi áp dụng Function khác null thì nó sẽ trả về một Optional chứa giá trị của kết quả sau khi áp dụng Function.
static <T> Optional<T> of(T value)
Trả về đối tượng Optional kiểu T chứa giá trị của value.
static <T> Optional<T> ofNullable(T value)
Trả về một Optional chứa giá trị được truyền vào nếu khác null, ngược lại sẽ trả về một Optional rỗng.
T orElse(T other)
Trả về giá trị của đối tượng Optional nếu có, ngược lại nó sẽ trả về đối tượng other mà bạn đã truyền vào phương thức này.
T orElseGet(Supplier<? extends T> other)
Trả về giá trị nếu tồn tại, ngược lại nó sẽ gọi other mà bạn đã truyền vào sau đó trả về kết quả của Supplier.
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
Nếu như đối tượng Optional có giá trị tồn tại thì nó sẽ trả về giá trị đó, ngược lại sẽ ném ra một Exception do chúng ta định nghĩa bởi Supplier đã truyền vào.
String toString()
Trả về một chuỗi non-empty biểu diễn cho đối tượng Optional. Thường thì chúng ta sử dụng nó cho mục đích debug.

Một số ví dụ về Optional

Tôi dám cá chắc rằng khi bạn đọc bảng trên sẽ thấy rất khó hiểu. Đó là điều đương nhiên bởi vì chúng ta cần thực hành để hiểu rõ hơn cách sử dụng của nó.
Khi nào thì dùng Optional
Ví dụ minh họa cho trường hợp này là khi bạn thực hiện một truy vấn đến cơ sở dữ liệu và bạn không thể chắc chắn rằng nó sẽ có dữ liệu trả về. Lúc này, bạn có thể tạo ra một đối tượng Optional để chứa giá trị có thể bị null này và thực hiện các phép toán khác.

Dưới đây là một ví dụ minh họa cho việc sử dụng một số phương thức được cung cấp bởi Optional.
Person.java
package com.vanthuong.tutorial.optional.example;

public class Person {
  private Long id;
  private String name;

  public Person(Long id, String name) {
    this.id = id;
    this.name = name;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

Application.java
package com.vanthuong.tutorial.optional.example;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

public class Application {
  public static void main(String[] args) {
    List<Person> persons = Arrays.asList(
        new Person(1L, "Nguyen Van A"),
        new Person(2L, "Nguyen Thi B"),
        new Person(3L, "Nguyen Van A"),
        new Person(4L, "Tran Nhu Nhong"),
        new Person(5L, "Nguyen Phan Dong"));

    Optional<Person> person = findFirstPerson(persons, p->p.getName().equalsIgnoreCase("nguyen van a"));

    // Method: isPresent()  
    if(person.isPresent()) {
      System.out.println(person.get().getName());
    } else {
      System.out.println("Not found person");
    }
    
    // Method: ifPresent()
    person = findFirstPerson(persons, p->p.getName().startsWith("Luu"));
    person.ifPresent(p->System.out.println(p.getName()));
    
    // Method: orElse()
    person = findFirstPerson(persons, p->p.getName().equals("Abc"));
    Person person2 = new Person(6L, "Person #6");
    Person person3 = person.orElse(person2);
    System.out.println(person3.getName());
    
    // Method: orElseThrow()   
    try {
      Person person4 = findFirstPerson(persons, p->p.getName().equals("Some name"))
          .orElseThrow(() -> new Exception("Not found person"));
      System.out.println(person4.getName());
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
  
  private static Optional<Person> findFirstPerson(List<Person> persons, Predicate<Person> predicate) {
    return persons.stream()
        .filter(predicate)
        .findFirst();
  }
}
Đầu tiên tôi xin giải thích trước về phương thức findFirstperson(). Nó nhận đầu vào là một danh sách các đối tượng Person và 1 Predicate để lộc dữ liệu cho danh sách Person, sau đó tìm và trả về đối tượng Person đầu tiên sau khi đã lọc qua phương thức findFirst(). Phương thức findFirst() sẽ trả về 1 Optional của Person. Vì như chúng ta biết, sau khi lọc danh sách theo một điều kiện nào đó, danh sách Person có thể sẽ bị rỗng nên nó sẽ return về đối tượng là Optional.

Tại dòng code bên dưới comment // Method: isPresent(), các bạn thấy rằng phương thức findFirst() sẽ trả về đối tượng đầu tiên có name là "Nguyen Van A". Phương thức isPresent() sẽ kiểm tra xem có giá trị không dể in ra màn hình, ngược lại sẽ in ra dòng "Not found person".

Dưới comment // Method: ifPresent(), tôi lọc để lấy đối tượng Person có name bắt đầu là "Luu". Tôi gọi hàm ifPresent() và truyền vào một consumer để in ra màn hình tên của đối tượng Person nếu tìm thấy.

Dưới comment // Method: orElse(), tôi lọc để lấy đối tượng đầu tiên có name là "Abc". Kết quả trả về là một Optional rỗng vì danh sách chẳng có ai có tên này cả. Lúc này tôi gọi phương thức orElse(), nếu không tồn tại thì trả về cho tôi đối tượng person2 mà tôi đã tạo ra trước đó.

Dưới comment // Method: orElseThrow(), tôi đang cố gắng lọc theo điều kiện để trả về một Optional rỗng. Lúc này tôi gọi phương thức orElseThrow() và truyền vào một Supplier để ném ra một Exception với message là "Not found person". Tôi đã catch Exception này lại để in ra màn hình message.

Cuối cùng, khi biên dịch và chạy chương trình kết quả in ra màn hình như sau:
Nguyen Van A
Person #6
Not found person

Hi vọng rằng với bảng phương thức mà tôi đã liệt kê và ví dụ minh họa ở trên, các bạn đọc giả có thể hiểu được cách dùng của Optional Class. Chúc các bạn học tốt nhé.