프로그래밍/Java

Optional

hyokeunLee 2022. 5. 5. 09:22

Optional 

Java8 에 추가된 새로운 인터페이스.

비어있을수도 있고, 값 하나를 담고 있을수도 있는 컨테이너 인스턴스의 타입이다. 

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

 

 

자바 프로그래밍에서 NullPointerException 을 보는 이유. 

  • null 를 리턴하니까! ( null 이 리턴되는 자체가 문제이다. )
  • null 체크를 깜빡했으니까!

 

 

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

  • 예외를 던진다. 
    • 에러가 발생하게되면 자바는 스택 트레이스를 찍는데, ( 이 에러가 발생하기 전까지의 어떠한 콜 스택을 거쳐서 에러가 발생하게 되었는지에 대한 정보) 이 자체로 리소스를 사용하는거여서 부담이 가기 때문에 필요할 때에만 사용해야지 로직을 처리할때 사용하는건 비용부담이 된다. 
  • null 를 리턴한다. 
    • 비용문제는 없지만 그 코드를 사용하는 클라이언트 코드가 주의해야한다.
  • Optional 을 리턴한다. 
    • 클라이언트에 코드에게 명시적으로 빈 값일 수도 있도있다는 걸 알려주고, 빈 값인 경우에 대한 처리를 강제한다.

 

 

주의할 것

  • 리턴값으로만 쓰기를 권장한다.
    • 메소드 매개변수 타입, 맵의 키의 타입, 인스턴스 필드 타입으로 쓰지 말자.
    • 맵의 키 타입에 Optional 을 준다는것은 맵의 특징을 무시하는 것이다. (키는 null 일 수 없다.)
  • Optional 을 리턴하는 메소드에서 null 을 리턴하지 말자. 
    • 리턴 할 것이 없다면 null 말고,  Optional.empty( ) 를 리턴하자.
  • 프리미티브 타입용 Optional 은 따로 있다. (OptionalInt, OptionalLong ...)
    • 프리미티브 타입에 Optional 을 사용할 경우 박싱, 언박싱이 발생한다. OptionalInt 을 사용하자.
  • Collection, Map, Stream Array, Optional 은 Optional 로 감싸지 말 것. 
    • 이미 해당 타입들은 비어있다는 것을 표현할 수 있는 컨테이너 성격을 가진 타입들이다.  

 

 

 

실습

package com.study.java8to11;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class OptionalStudy {

  public static void main(String[] args) {
    List<OnlineClass> springClasses = new ArrayList<>();
    springClasses.add(new OnlineClass(1, "spring boot", true));
    springClasses.add(new OnlineClass(5, "rest api dev", false));


    Optional<OnlineClass> optional = springClasses.stream()
        .filter(oc -> oc.getTitle().startsWith("spring")) // 있을 수도 있고 없을 수도 있어서 리턴 타입은 Optional 이다.
        .findFirst();

    // [1] Optional 에 값이 있는지 없는지 확인하기
    boolean present = optional.isEmpty();
    boolean empty = optional.isPresent();

    // [2] Optional 에 있는 값 가져오기
    // [2-1] 비어있는 Optional 에서 무언가 꺼낼땐 NoSuchElementeException 발생
    // System.out.println(optional.get());
    // [2-2] 예외처리 적용 , 하지만 이렇게 get 으로 가져오고 isPresent 로 예외처리 하는 방식은 잘 사용되지 않는다.
    if (optional.isPresent()) {
      System.out.println(optional.get());
    }

    // [3] Optional에 값이 있는 경우 그 값을 가지고 ~를 하라.
    // ex) spring 으로 시작하는 수업이 있으면 id 를 출력하라
    optional.ifPresent(oc -> {
      System.out.println(oc.getId());
    });

    // [4] Optional에 값이 있으면 가져오고 없는 경우에 ~를 리턴하라
    // ex) jpa 로 시작하는 수업이 없다면 비어있는 수업을 리턴하라.
    // orElse(T) 함수 구현체가 아닌, 인스턴스를 매개변수로 주어야한다.
    // orElse 는 optional 에 값이 있던, 없던 무조건 실행된다.
    OnlineClass onlineClass = optional.orElse(createNewClasses());
    System.out.println(onlineClass.getTitle());

    // [5] Optional에 값이 있으면 가져오고 없는 경우에 ~를 하라.
    // ex) JPA로 시작하는 수업이 없다면 새로 만들어서 리턴하라.
    // orElseGet(Supplier)  supplier 로 구현해주면 된다. (람다 혹은 메서드 레퍼런스)
    // orElseGet 는 optional 에 값이 없는 경우에만 실행된다.
    // orElse 는 이미 만들어져 있는 인스턴스, 상수 들을 참고해서 사용할땐 orElse 가 적합
    // orElseGet 동적으로 무언가 작업해서 만들어내야 한다면 orElseGet 이 적합
    OnlineClass onlineClass2 = optional.orElseGet(OptionalStudy::createNewClasses);
    System.out.println(onlineClass2.getTitle());

    // 무언가 만들어줄 수 없는상황에는 orElseThrow
    // [6] Optional에 값이 있으면 가져오고 없는 경우 에러를 던져라.
    // default 로는 NoSuchElementException 을 던지지만, 원하는 에러가 있을경우 supplier 로 구현해주면 된다.
    OnlineClass onlineClass3 = optional.orElseThrow(IllegalArgumentException::new);

    // [7] Optional에 들어있는 값 걸러내기
    // 값이 있다는 가정하에 이벤트가 일어나며, 값이 없을경우 아무 일도 일어나지 않는다.
    Optional<OnlineClass> onlineClass4 = optional.filter(OnlineClass::isClosed);
    System.out.println(onlineClass4.isEmpty()); // true

    // [8] Optional에 들어있는 값 변환하기
    // [8-1] Optional map(Function)
    Optional<Integer> integer = optional.map(OnlineClass::getId); // id 타입을 담고있는 Optional 반환

    // [8-2] Optional flatMap(Function): Optional 안에 들어있는 인스턴스가 Optional인 경우 사용하면 편리하다.
    Optional<Progress> progress = optional.flatMap(OnlineClass::getProgress);

    // [8-3] Optional 안에 있는 값이 Optional 인데 map 을 사용하게 될 경우, 아래와 같이 두번 작업을 해야한다.
    Optional<Optional<Progress>> progress1 = optional.map(OnlineClass::getProgress);
    Optional<Progress> progress2 = progress1.orElse(Optional.empty());

    // 무조건 .get 으로 꺼내기 보다는 여태 배운 메소드를 활용하여 적용하자.

  }

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

 

 

 

참고

'프로그래밍 > Java' 카테고리의 다른 글

CompletableFuture (1) - 자바 Concurrent 프로그래밍 소개  (0) 2022.05.06
Date와 Time API  (0) 2022.05.05
Stream  (0) 2022.05.04
Lombok 동작원리  (0) 2022.05.03
다이나믹 프록시  (0) 2022.05.03