[Java] Optional 값 생성, 획득, 처리, orElseGet(), 실무 Optional 사용

벼랑 끝 코딩·2025년 4월 23일
0

Java

목록 보기
35/40
post-thumbnail

Optional 사용 이유

field를 확인할 때 null이 반환될지 값이 반환될지 예상하기 어렵다.
이 때 null인 경우라면 접근 시 NPE(NullPointerException)가 발생한다.
예외를 발생하지 않기 위해 if문을 사용하면 곧 가독성 저하로 이어진다.

Optional은 Java 8부터 등장한 존재하지 않을 수도 있는 값을 감싸는 컨테이너 클래스이다.

Optional을 사용하면 의도를 분명하게 전달하여 가독성을 향상시키고
빈 값을 반환하여 예외를 발생시키지 않는다.

Optional 생성

// 내부 값이 확실하게 null이 아닌 경우, null 전달 시 NPE 예외 발생
Optional.of(T value);

// 내부 값이 null일 수도, 아닐 수도 있는 경우, null 전달 시 Optional.empty 반환
Optional.ofNullable(T value);

// 내부 값이 null인 경우, Optional.empty 반환
Optional.empty();

값이 null인지 아닌지에 따라 of(), ofNullable(), empty() 메서드로 나눌 수 있다.

Optional 값 획득

Optional 객체에 담긴 값을 꺼내는 방법은 여러가지가 있다.

Optional<T> optional = Optional.of();

// isPresent, isEmpty(Java 11부터) : 값이 있는지, 없는지 결과 반환
boolean result = optional.isPresent();


// get : 값이 있는 경우 반환, 없으면 예외 발생
// 예외를 발생시켜 사용 시 주의해야 하며 주로 orElse 사용
T value = optional.get();


// orElse : 값이 있는 경우 반환, 없으면 지정된 값 반환
T value = optional.orElse(nullValue);


// orElseGet : 값이 있는 경우 반환, 없으면 Supplier 생성 값 반환
T value = optional.orElseGet(Supplier<T> supplier);


// orElseThrow : 값이 있는 경우 반환, 없으면 지정된 예외 반환
// 예외를 설정하지 않는 경우 NoSuchElementException 반환
T value = optional.orElseThrow(Supplier<Throwable> supplier);


// or : 값이 있는 경우 Optional 그대로 반환, 없으면 Supplier 생성 Optional 반환
Optional<T> value = optional.or(Supplier<Optional<T>> supplier);

orElse()와 orElseGet() 차이

Optional에서 값을 획득하는 메서드 중 orElse()와 orElseGet()
매개변수로 전달되는 것이 인지, 함수형 인터페이스인지에 차이가 있을 뿐,
지정된 값을 반환하는 것에는 차이가 없는데 왜 두 메서드를 구분한 것일까?

즉시 평가와 지연 평가

Optional<T> optional = Optional.of();

// 값이 비어있지 않아도 nullValueMethod() 항상 수행
Optional<T> value = optional.orElse(nullValueMethod());

// supplier의 최종 연산 메서드를 호출해야 람다식 실행
Optional<T> value = optional.orElseGet(Supplier<R> supplier);

메서드를 구분한 이유는 지연 평가로 연산을 최적화하여 성능을 향상시키기 위해서이다.

Java는 기본적으로 메서드 호출 즉시 연산을 수행하는 즉시 평가 방식을 제공한다.
orElse() 메서드는 값을 전달하기 때문에 즉시 평가가 적용된다.
값이 비어있지 않아도 orElse()의 매개변수 값을 항상 계산하여
값이 비어있는 경우 불필요한 연산이 수행되는 문제가 발생한다.

반대로 람다최종 연산이 호출되어야만 중간 연산이 수행되는 지연 연산 방식을 제공한다.
orElseGet() 메서드는 람다를 전달하기 때문에 지연 평가가 적용된다.
람다 전달 후 최종 연산이 호출되지 않으면 연산이 수행되지 않아 성능을 최적화할 수 있다.

값이 존재하지 않을 가능성이 높거나 생성 비용이 크지 않는 경우
orElse()를 사용하여 복잡한 람다를 작성하는 대신 가독성이 좋은 코드를 유지한다.
하지만 생성 비용이 크거나 값이 들어있을 확률이 높은 경우
orElseGet()을 사용하여 지연 연산을 활용해 성능을 최적화해야 한다.

Optional 값 처리

Optional은 값이 존재하는 경우와 존재하지 않는 경우에 따라
다르게 처리할 수 있는 다양한 방법을 제공한다.

Optional<T> optional = Optional.of();

// ifPresent : 값이 있으면 실행, 없으면 무시
optional.ifPresent(Consumer<T> consumer);


// ifPresentOrElse : 값이 있으면 Consumer 실행, 없으면 Runnable 실행
optional.ifPresentOrElse(Consumer<T> consumer, Runnable runnable);


// map : 값이 있으면 Function 실행 결과 반환, 없으면 Optional.empty 반환
Optional<R> result = optional.map(Function<T, R> function);


// flatMap : map과 유사하나 중첩된 Optional을 단일 Optional로 반환
Optional<R> result = optional.flatMap(Function<T, Optional<R>> function);


// filter : 값이 있고 조건을 만족하면 그대로 반환, 값이 없거나 조건을 불만족하면 Optional.empty 반환
Optional<T> result = optional.filter(Predicate<T> predicate);


// stream : 값이 있으면 하나의 데이터가 담긴 스트림 반환, 없으면 빈 스트림 반환
Stream<T> stream = optional.stream();

Optional 실무 사용

Optional은 무작정 사용하기 보다 적절한 상황에 사용해야만 한다.

메서드 반환 값

Optional은 메서드의 반환 값에만 사용하는 것이 원칙이다.

// Optional 필드 선언
Optional<T> value = null;  // Optional 자체가 null인 경우
value.get() // NPE 예외 발생


// Optional 메서드 매개 변수에 선언
public void method(Optional<T> optional) {
	// 코드
}
// Optional이 매개변수인 메서드 사용 시 호출자가 Optional을 생성해야 하는 번거로움
T value = Optional.of();
method(value);

Optional을 필드에 선언하면 Optional 자체가 null일 때 마찬가지로 NPE가 발생한다.
매개 변수에 선언하면 호출자가 Optional 객체를 생성해야 하는 부담이 생긴다.
따라서 Optional은 메서드의 반환 값에만 사용하도록 권장된다.

Collection

// Collection에 Optional 사용
public Optional<Collection<T>> method() {
	// 코드
}

Collections.empty(); // Collection은 그 자체로 empty 상태 표현 가능

Collection은 Optional로 포장하지 않는다.

Collection은 empty를 표현할 수 있기 때문에 Optional을 사용할 이유가 없다.

isPresent() + get()

Optional<T> optional = Optional.of(data);

if (optional.isPresent()) {
	T value = optional.get();
}

Optional의 isPresent()와 get() 메서드를 조합하여 로직을 구성하지 않아야 한다.

get()은 값이 null인 경우 NPE 예외를 반환하기 때문에
사전에 값이 null이 아닌지 확인하기 위해 isPresent() 메서드를 사용해야 하는데,
이 경우 Optional이 아닌 일반 객체를 사용하는 것과 차이가 없기 때문이다.

get() 대신 orElse(), orElseGet()을 사용하고,
그럼에도 불구하고 get()을 사용해야 한다면 isPresent()를 함께 사용해야 한다.

Optional 기본형 타입

OptionalInt, OptionalLong, OptionalDouble로 Optional 기본형 타입이 존재하지만
기본형 타입은 거의 사용하지 않는다.

마무리

Optional은 메서드의 반환 값에 사용하는 것이 원칙이다.
즉 서버가 아닌 클라이언트 입장에서 Optional을 반환 받아 어떻게 처리할 지 중요하다.
클라이언트가 값이 null일 수 있다는 것을 인지할 필요가 있는지,
그 전에 메서드가 null을 반환할 확률이 있는지,
Optional을 반환하는 것보다 예외를 던지는 것이 적절한건 아닌지 생각해보자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글