Optional 소개

자바 프로그래밍에서 NullPointerException과 같은 에러들을 종종 볼 수 있다. 이러한 에러의 경우 컴파일 에러가 아닌 런타임 에러로 프로젝트에서 발생하면 치명적일 수 있는 에러이다. 

 

보통 NullPointerException의 경우 아래와 같은 상황에서 발생한다.

  • Null 리턴 (설계상)
  • Null 체크 실수 (설계상)

나도 코딩테스트를 보며 NullPointerException을 가끔 본다. 이럴 때마다 설계의 중요성을 한번씩 깨닫게 되는 것 같다..

 

하지만, 메소드에서 작업 중 특별한 상황에서 값을 제대로 리턴을 할 수 없는 경우가 있을 수 있다. 이러한 경우에는 아래와 같은 방법이 있을 것이다.

  • 예외를 던진다. (비싸다.. 스택트레이스(에러가 발생하기 전까지의 콜스타일 저장)를 찍어두기때문이다.)
    • 로직을 처리할 때 Exception처리는 좋은 로직이 아니다!
  • null을 리턴한다. (비용 문제가 없지만, 코드를 사용하는 클라이언트 코드에서 주의해야 한다.)
  • Optional을 리턴한다. (클라이언트에 코드에게 명시적으로 빈 값일 수도 있다는 것을 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

null을 리턴하는 경우 설계상 실수가 있을 수 있지만, Optional을 사용한다면 실수를 없앨 수 있다고 생각이 든다. 그렇다면 Optional을 알아보자.

 

Optional

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

 

주의할 점

  • 리턴값으로만 쓰기를 권장 (메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자.)
    • 메소드 매개변수 타입) 매개변수로 받았지만, 메소드 안에서 다시 체크를 해야하기 때문에 코드가 길어지는 문제가 발생
    • 맵의 키 타입) 맵 키의 특징 중 하나는 키가 Null이 아닌 것 이다. 이를 무너뜨린다..
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
  • 프리미티브 타입용 Optional은 따로 있다. OptionalInt, OptionalLong....
    • 프리미티브 타입의 경우 Optional.of(10)과 같이 사용하면 박싱, 언박싱이 안에서 이루어지기 때문에 성능이 안좋다. 이를 해결하고자 OptionalInt, OptionalLong, OptionalDouble과 같은 Optional을 사용하자.
  • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말자.
    • 이미 container의 성격을 갖고 있다. 굳이 감쌀 이유가 없다.

 

Optional 만들기

  • Optional.of(instance)
    • .of 는 instance가 Null이 무조건 아닐 때 사용
  • Optional.ofNullable(instance) 
    • .ofNullable 은 instance가 null일 수도 있을 때 사용
  • Optional.empty()

 

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

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

 

Optional에 있는 값 가져오기

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

 

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

  • isPresent(instance)
  • ex) Spring으로 시작하는 수업이 있으면 id를 출력하라

 

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

  • orElse(T)
  • ex) JPA로 시작하는 수업이 없다면 비어있는 수업을 Return

 

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

  • orElseGet(Supplier)
  • ex) JPA로 시작하는 수업이 없다면 새로 만들어서 Return

 

Optional에 갑싱 있으면 가져오고 없는 경우 에러를 던져라

  • orElseThrow()

 

Optional에 들어있는 값 걸러내기

  • Optional filter(Predicate)

 

Optional에 들어있는 값 변환하기

  • Optional map(Function)
  • Optional flatMap(Function)
    • Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리 (안에 있는 Optional이 한번 까져서 나옴)

코드로 확인해보자.

 

 

package me.qazyj.java8;

import java.time.Duration;
import java.util.*;

public class OptionalTest {

    public static void main(String[] args) {

        List<OnlineClass> springClass = new ArrayList<>();
        springClass.add(new OnlineClass(1, "spring boot", true));
        springClass.add(new OnlineClass(6, "rest api", false));

        Optional<OnlineClass> findStartSpring = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();       // 찾은 첫번째 값을 return
        System.out.println(findStartSpring.isPresent());        // id 1이 spring으로 시작함 ==>> true
        //값이 있는지 없는지 확실하지 않을 때 ifPresent로 확인 후 있을 떄만 실행
        //findStartSpring.ifPresent(oc -> System.out.println(oc.getTitle()));

        //없으면 생성 x(있어도 생성하고 Return 함) orElse
        Optional<OnlineClass> findStartJpa = springClass.stream()
                .filter(oc -> oc.getTitle().startsWith("jpa"))
                .findFirst();       // 찾은 첫번째 값을 return
        OnlineClass onlineClass = findStartJpa.orElse(new OnlineClass(10, "New class", false));
        System.out.println(onlineClass.getTitle());

        // 에러
        //OnlineClass onlineClass1 = findStartJpa.orElseThrow(IllegalStateException::new);

        // is.empty - 비어있을 떄 true / 있을 떄 false
        Optional<OnlineClass> onlineClass2 = findStartSpring.filter(oc -> oc.getClosed());
        System.out.println("empty : " + onlineClass2.isEmpty());

    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

[Java 8] CompletableFuture  (0) 2022.10.13
[Java 8] Date와 Time  (0) 2022.10.12
[JAVA 8] Stream  (0) 2022.10.06
[JAVA 8] 인터페이스의 변화  (0) 2022.10.06
[JAVA 8] 함수형 인터페이스와 람다 표현식  (0) 2022.09.23

+ Recent posts