[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) { }