Monday

[Java8] Optional 이란? 본문

언어/Java

[Java8] Optional 이란?

뉴비2 2022. 4. 15. 22:06

1. Optional이란?

Optional이란 Null 참조 문제를 해결하기 위해 Java8에 추가된 개념입니다. Optional<T> 형식으로, 어떤 값을 Optional이 감싸고 있는 형태로 사용됩니다. Optional로 감쌌다는 것은 T라는 값이 있을 수도 있고 없을 수도 있다는 의미를 나타냅니다. 즉, 포함할 수 있는 값은 1개 이하이며 T가 null일 수도 있고 null이 아닐 수도 있음을 의미하는데, 이를 적용함으로써 null 참조 오류를 줄일 수 있습니다. 예를 들어, Optional<String>에서 String 값이 null이라면 Optional<String>은 null인 값을 감싸고있는 Optional 객체입니다. 따라서, String 값이 null이라도 개발자는 Optional 객체를 참조하는 것이고, 이를 통해서 null 참조 오류를 해결할 수 있습니다.

2. Optional 객체 생성 방법

Optional 객체는 다음과 같이 생성 할 수 있습니다.

생성 방법 사용 시기
Optional.empty() 빈 Optional 객체를 생성할 때 사용
Optional.of(value) value 값이 null이 아니면 Optional 객체 생성.
value 값이 null이라면 NullPointerException 발생.
Optional.ofNullable(value) value 값이 null이 아니면 Optional 객체 생성.
value 값이 null이라면 빈 Optional 객체 생성.

3. Optional 연산

Optional은 Stream과 유사하게 map, filter와 같은 연산을 제공합니다. 각 연산의 의미와 사용법은 다음과 같습니다.

자세한 내용은 java.util.Optional을 살펴보면 더 쉽게 이해하실 수 있습니다.

연산 의미 사용법 or 메소드 정의
get 값이 존재하면 값 반환, 없으면
NoSuchElementException 발생
Optional<String> s = myString;
String value = s.get(myString)
filter 값이 존재하고 조건과 일치하는 값이 있다면 Optional 객체 반환,
없으면 빈 Optional 객체 반환
Optional<person> p = myPerson;
p.filter(p -> p.getAge() > minAge)
ifPresent 값이 존재하면 인자로 넘어 온 Consumer 함수 실행.
값이 없으면 아무 일 일어나지 않음
ifPresent(Consumer<? super T> action)
ifPresentOrElse 값이 존재하면 인자로 넘어 온 Consumer 함수 실행.
값이 없으면 emptyAction 실행
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
isPresent 값이 존재하면 true 없으면 false public boolean isPresent() {
        return value != null;}
or 값이 존재하면 자기 자신 Optional 반환.
값이 없으면 Supplier 실행한 결과를 반환 
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
orElse 값이 존재하면 값을 반환
값이 없으면 기본값 반환
public T orElse(T other) {
        return value != null ? value : other;}
orElseGet 값이 존재하면 값을 반환
값이 없으면 Supplier 적용한 결과 반환
public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();}
orElseThrow 값이 존재하면 반환.
없으면 NoSucheElementException 에러 발생
public T orElseThrow() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;}

4. Optional에서 map과 flatMap

Optional로 감싸진 Optional 객체에 map 메서드를 적용하면, 감싸진 값에 map 함수가 적용되고 그 결과를 새로운 Optional 객체가 감싼 결과가 반환됩니다. 반면에, flatMap 메서드는 적용 결과를 새로운 Optional 객체로 감싸지 않습니다. 이 차이를이해하기 쉽게 살펴보면, map 함수는 2번 연속해서 적용했을 때 결과가 Optional<Optional<T>>와 같은 결과가 나옵니다. 하지만, flatMap은 여러 번 적용해도 Optional<T> 형태만 나옵니다. 그 이유는 map 함수와 flatMap 함수의 정의를 보면 더 자세히 알 수 있습니다. 아래와 같이 map 메서드는 T -> U 로 변환하는 mapper가 인자(parameter)로 들어오고, 그 결과를 새로운 Optional 객체로 둘러싸서 반환합니다. 하지만, flatMap 메서드는 T -> Optional<U> 로 변환하는 mapper만 인자로 들어오고, 그 결과를 그대로 반환합니다. 따라서, map 메서드는 mapper가 여러 번 사용되면 Optional이 중첩된 결과를 만들 수 있지만, flatMap은 늘 Optional이 1개로만 감싸진 결과를 반환하게 됩니다.

    // map 함수
    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));
        }
    }
    
    // flatMap 함수
   public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            @SuppressWarnings("unchecked")
            Optional<U> r = (Optional<U>) mapper.apply(value);
            return Objects.requireNonNull(r);
        }
    }

5. Java 9 Optional의 stream 메서드

Java 9에서는 stream 처리 중 Optional을 포함하는 스트림이 등장할 때 편하게 처리할 수 있게 stream()이라는 메소드가 추가되었습니다. 보통 일반적인 스트림 요소를 조작하면 map, filter 등 여러 긴 체인을 거치는데 중간에 Optional이 등장하면 처리가 다소 복잡해집니다. Optional이 등장한다면, 우선 Optional 안에 감싸진 값이 있는지 확인해야하고, get함수를 이용해 감싸진 값을 가져와야 최종 Collection으로 반환할 수 있습니다. 이 과정을 stream 메서드를 이용해 단축시켜줍니다. 예를 통해 살펴보는 것이 이해가 쉬우므로 예제를 사용해 살펴보겠습니다.

Stream<Optional<String>> stream = .... // Optional 스트림 등장
Set<String> result = stream.filter(Optional::isPresent())
	.map(Optional::get)
	.collect(toSet());
                            
//위와 동일한 동작을 하는 stream 메서드
Stream<Optional<String>> stream = .... // Optional 스트림 등장
Set<String> result = stream.flatMap(Optional::stream)
	.collect(toSet());
Comments