Link Search Menu Expand Document

섹션 4. Optional

Optional 소개

자바 프로그래밍에서 NullPointerException을 종종 보게 되는 이유

  • null을 리턴하니까! && null 체크를 깜빡했으니까!

메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택할 수 있는 방법

  • 예외를 던진다. (비싸다, 스택트레이스를 찍어두니까.)
  • null을 리턴한다. (비용 문제가 없지만 그 코드를 사용하는 클리어인트 코드가 주의해야 한다.)
  • (자바 8부터) Optional을 리턴한다. (클라이언트에 코드에게 명시적으로 빈 값일 수도 있다는 걸 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

Optional

● 오직 값 한 개가 들어있을 수도 없을 수도 있는 컨테이너

  • ofNullable(): Optional 내부 값이 null일 수도 있다
  • of() : Optional 내부 값이 null이 아니다

주의점

  • 리턴값으로만 쓰기를 권장한다. (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
    • 매개변수로 Optional을 사용할 경우 null 값이 들어오면 NPE가 발생하므로 Optional 사용하는 의미가 없음
    public void setProgress(Optional<Progress> progress) {
      if (progress.isPresent()) { // progress가 null일 경우 NPE 발생
          progress.ifPresent(p -> this.progress = p);
      }
    }
    
    • Map의 Key Type은 null이면 안되므로 null일 수도 있는 Optional을 사용하면 안됨
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.

    • 매개변수로 Optional을 사용한 경우와 마찬가지로 메소드에서 null 값이 리턴되면 클라이언트에서 Optional.isPresent()를 사용할 수 없다. Optional.enmpty()를 리턴하자.
  • 프리미티브 타입용 Optional이 따로 있다. OptionalInt, OptionalLong ,…

    • 그냥 Optional을 사용할 경우 Boxing-Unboxing이 일어나 성능이 저하된다.
  • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말 것.

Optional API

Optional 만들기

  • Optional.of()
  • Optional.ofNullable()
  • Optional.empty()

Optional에 값이 있는지 없는지 확인하기

  • isPresent()
  • isEmpty() (Java 11부터 제공)

Optional에 있는 값 가져오기

  • get()
  • 만약에 비어있는 Optional에서 무언가를 꺼낸다면?
    • NoSuchElementException 발생

Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하라.

  • ifPresent(Consumer)
  • 예) Spring으로 시작하는 수업이 있으면 id를 출력하라.

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 리턴하라.

  • orElse(T)
  • ex) JPA로 시작하는 수업이 없다면 비어있는 수업을 리턴하라.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Practice {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));

        Optional<OnlineClass> optional = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("rest"))
                .findFirst();

        boolean present = optional.isPresent();
        System.out.println(present);

        OnlineClass onlineClass = optional.orElse(createNewClass());

    }

    private static OnlineClass createNewClass() {
        System.out.println("creating new online class");
        return new OnlineClass(10, "New class", false);
    }
}
// false                     // true
// creating new online class // creating new online class

orElse(T)가 동작하지 않아도 createNewClass()는 동작함. Optional이 값을 리턴하지 않을 뿐이다.

이미 만들어진 Instance를 참조할 경우에 사용하면 좋다.

Optional에 값이 있으면 가져오고 없는 경우에 ~~를 하라.

  • orElseGet(Supplier)
  • 예) JPA로 시작하는 수업이 없다면 새로 만들어서 리턴하라.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Practice {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));

        Optional<OnlineClass> optional = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();

        boolean present = optional.isPresent();
        System.out.println(present);

        OnlineClass onlineClass = optional.orElseGet(Practice::createNewClass);

    }

    private static OnlineClass createNewClass() {
        System.out.println("creating new online class");
        return new OnlineClass(10, "New class", false);
    }
}
// false                      // true
// creating new online class

Optional에 값이 있으면 가져오고 없는 경우 에러를 던져라.

  • orElseThrow()

Optional에 들어있는 값 걸러내기

  • Optional filter(Predicate)

    Optional에 들어있는 값 변환하기

  • Optional map(Function)
  • Optional flatMap(Function): Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리하다.
        Optional<Optional<Progress>> progress = optional.map(OnlineClass::getProgress);
        Optional<Progress> progress2 = progress.orElse(Optional.empty());

        Optional<Progress> progress1 = optional.flatMap(OnlineClass::getProgress);