본문 바로가기

연재작/프로그래밍 언어

Java - Optional 뽀개기 (2)

지난번 글에서는 optional의 의의와 생성까지 다루어 보았다.

이번 글에서는 optional이 다루는 중간연산과

가장 중요한 최종 연산 및 null이 있을 경우와 없을 경우 값을 어떻게 다룰 지를 써보겠다.

 

+ Optional의 경우, context를 유지하는 것이 가장 중요하기 때문에

각각의 method에서 값을 처리하기 전에 항상 isPresent()를 통해 있는지 없는지를 확인한다.

isPresent 메서드는 간단히 말해 해당하는 Optional의 값이 empty인지 아닌지를 체크하는 메서드이다.

Optional이 모나드 패턴, 즉 context를 유지하기 위해서 사용하는 방식인 듯 하다.

 

2. stream에 비해 처리할 게 적어진 중간 연산 메서드.

Optional은 stream과 같이 중간 연산에 대해서 다루는게 많지는 않다.

중간 연산같은 경우, stream으로 전환되는 메서드가 제공되기에 이를 잘 활용해봐야 할 듯 하다.

 

또한, 원래부터 null처리 및 값의 처리에 특화되어 있는 객체이기 때문에 이와 관련된 메서드가 적다.

(1) filter : 값을 필터링

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

만약 주어진 predicate 함수의 조건을 만족할 경우에는 값을 유지하고,

그렇지 않을 경우 Optional.empty()를 반환한다.

 

만약 optional안의 값이 null일 경우에도 (optional.empty()인 경우)

함수를 실행하지 않고 Optional.empty()를 반환한다.

 

+ 복습) Predicate :  T라는 타입에 대해 boolean을 반환하는 함수형 인터페이스

 

(2) 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));	}	}

 

 

map의 경우, Optional로 싸여진 값을 다른 값으로 변환할 때 사용된다.

Stream에서의 사용과 동일하다.

 

 

(3) flatMap : 모나드 패턴에서 사용하기 위한 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); } }

 

T ➡️ Optional<T> 에서 T값에 대해 한번 더 Optional < Optional< T > > 처럼 처리 할 때,

이를 눌러서 Optional <T>  로 평탄화 작업?을 진행해주는 함수이다.

 

3. optional의 최종연산(처리) 

java 9 부터 stream으로 전환이 가능해져서 collection 타입의 병렬 처리는 stream으로 진행하는 것이

가능해졌다. 따라서 stream 메서드를 먼저 소개하도록 하겠다.

 

(1) Stream : Optional<T> 를 Stream<T>로 변환 (Java 9 이상부터 지원)

public Stream<T> stream() { 
	if (!isPresent()) { return Stream.empty(); } 
    else { return Stream.of(value); } }

 

주의할 점은 T 또한 각각의 값이 반드시 Present, 즉 empty가 아니여야만 진행이 된다는 것이다.

그 이후의 사용 방식은 이전 글에서 다뤘던 Stream 관련 글을 참조하면 될 것이다.

 

(2) get : Optional에 값이 있을 경우 그 값을 반환. 

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

주의 할 것은 반드시 있을 경우에만 get 메서드를 사용해야 한다는 것이다.

그렇지 않으면 NoSuchElementException이 발생하기에 힘들여서 Optional을 사용한 의미가 없어진다.

 

(3) isPresent : 값이 있는지 없는지의 여부를 boolean으로 반환한다.

public boolean isPresent() { return value != null; }

 

 

(4) isEmpty : 값이 없을 때 boolean으로 반환한다.

public boolean isEmpty() { return value == null; }

 

(5) ifPresent : 값이 있을 경우 return을 하지 않는 함수( Consumer )를 실행한다.

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

 

(6) ifPresentOrElse : 값이 있을 경우 Consumer를 실행, 없을 경우 fallback 함수를 실행한다. 

public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    if (value != null) { action.accept(value); } else { emptyAction.run(); } }

따라서 파라미터로 두 개의 함수형 인터페이스를 집어넣어야 한다.

참고로 Runnable 인터페이스에서 run이라는 기본 함수형 인터페이스를 집어넣어야 한다.

그런데 run은 우리가 알고 있는 기본 람다 표현식인

() -> {}

 

와 같은 형식이기에, 굳이 구현할 필요는 없겠다.

 

(7) orElse : 값이 있으면 그 값을, 없으면 failover값을 반환

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

 

(8) orElseGet : 값이 있으면 그 값을 반환하고, 없으면 supplier를 호출해 failover을 반환한다.

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

 

(9) orElseThrow : 값이 있으면 값을 반환, 없으면 exception을 던진다. 

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

여기서 overload된 전자의 메서드의 경우, throw를 직접적으로 넣는 것이 아니라,

이를 throw할 supplier, 즉 함수형 인터페이스를 넣어야 한다.

 

 

써보면서 느끼는게, Optional은 우리가 해야할 null처리를 대신 미리 해줘서

외부 클래스인 Optional에서 미리 정의해놨기에 우리가 사용하기 편한 환경이 제공 되고 있었다.

편하게 사용 할 수 있도록 제공이 되었으니, 이를 잘 써봐야겠다.