[JAVA] 옵셔널 (Optional)

Java Optional

1. 개요

A container object which may or may not contain a non-null value

  • 레퍼런스 객체를 가진 일종의 Wrapper Class 로써 해당 객체의 값이 null인지 아닌지 확인하고, null에 대한 처리를 별도의 로직 없이 처리하기 위한 것이다.
  • 내부적으로 java.util.function 의 객체들을 사용하고 있어, Stream 객체와 유사하게 동작할 수 있다. (1개의 값을 갖는 Stream 이라고 할 수 있다)

Optional is primarily intended for use as a method return type where there is a clear need to represent “no result,” and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

  • Optional은 주로 “결과 없음"을 나타내야하는 명확한 필요성이 있고 Null을 사용하면 오류가 발생할 가능성이있는 메서드 반환 유형으로 사용하기위한 것.
  • Optional 타입의 변수는 자체적으로 null이 아니어야 하며, 항상 Optional 인스턴스를 가리켜야한다. ( Optional optStr = null 이면 안됨)

2. Optional 기본 활용

1. 값이 없는 Optional 초기화 및 데이터 조회

Optional<String> optNull1 = Optional.of(null);          // NPE 발생  
Optional<String> optNull2 = Optional.ofNullable(null);

System.out.println(optNull2);        // Optional.empty 출력 
System.out.println(optNull2.get());  // NoSuchElementException 예외 발생 

2. 값이 있는 Optional 초기화 및 데이터 조회

String str = "Coupang";
Optional<String> optStr = Optional.of(str);  
System.out.println(optStr);         // Optional[Coupang] 출력 
System.out.println(optStr.get());   // Coupang 출력 

3. 기타 Optional 메소드

// orElse - 값이 없는 경우 특정 값을 반환.
System.out.println(optNull2.orElse("empty"));  
System.out.println(optStr.orElse("empty"));

// orElseGet - 값이 없는 경우 Supplier를 통해 값을 생성하여 반환. 
System.out.println(optNull2.orElseGet(String::new));

// orElseThrow - 값이 없는 경우 Supplier를 통해 예외를 생성하여 던짐. 
System.out.println(optNull2.orElseThrow(NoSuchFieldError::new));

// isPresent - Optional의 값이 존재하는지(empty가 아닌지) 확인. 
System.out.println(optNull2.isPresent());
System.out.println(optStr.isPresent());

// ifPresent - Optional의 값이 존재하는 경우 Consumer를 통해 입력값을 처리.
optNull2.ifPresent( v -> System.out.println(v));
optStr.ifPresent(System.out::println);

3. Optional 구현 소스

  • Optional 구현 소스를 보게되면 Stream 인터페이스와 유사하게 map, filter, flapMap 등을 제공하고 있는 것을 알 수 있다.
  • 이를 통해서 선언적으로 Optional 데이터를 처리할 수 있다.

package java.util;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public final class Optional<T> {

    private static final Optional<?> EMPTY = new Optional<>();
    private final T value;

    private Optional() {
        this.value = null;
    }

    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }
    
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    public T orElse(T other) {
        return value != null ? value : other;
    }

    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
}

4. Optional 활용 예시


package week1.selfstudy;

import week1.lec2.Movie;
import java.util.Optional;

public class OptionalSelfStudy {
    static public void main(String[] args) throws Exception {

        Movie movie1 = new Movie(1,"No Time To Die", true);
        Movie movie2 = new Movie(2,"Ever Since We Love", false);

        //TODO: 시나리오 - release된 영화인 경우에만 외부에 title명 제공해야 한다.
        printOptString(getReleasedMovieTitle(movie1));
        printOptString(getReleasedMovieTitle(movie2));
        printOptString(Optional.ofNullable(null));

        Optional.of(movie1).ifPresent(m -> System.out.println(m.getTitle()));

        printOptStringorThrow(Optional.ofNullable(null));

    }

    static Optional<String> getReleasedMovieTitle(Movie movie){
        return Optional.ofNullable(movie).filter(m-> m.getReleased()).map(Movie::getTitle);
    }

    static void printOptString(Optional<String> opt){
        System.out.println(opt +":"+opt.orElse("not released"));
        /**
        System.out.println(opt + ":" + opt.orElseGet( () -> "not released") );
        if(opt.isPresent()) {                 // 명시적으로 ifPresent로 대체하여 처리 가능.
            System.out.println(opt.get());
        }
        **/
    }

    static void printOptStringorThrow(Optional<String> opt) throws Exception {
        System.out.println(opt + ":" + opt.orElseThrow(() -> new Exception("No such movie Exception")) );
    }
}

5. 참고사항

  • Collectors의 일부 메소드는 리턴타입으로 Optional 를 리턴한다.
  • Optional 로 리턴하지 않으나 Optional이 필요한 경우 Optional.of, Optional.ofNullable 로 생성하여 사용한다.

 public static <T> Collector<T, ?, Optional<T>>
    minBy(Comparator<? super T> comparator) {
        return reducing(BinaryOperator.minBy(comparator));
    }

    public static <T> Collector<T, ?, Optional<T>>
    maxBy(Comparator<? super T> comparator) {
        return reducing(BinaryOperator.maxBy(comparator));
    }

 public static <T> Collector<T, ?, Optional<T>>
    reducing(BinaryOperator<T> op) { }