본문 바로가기
개발 - Coding/Java & Spring

[Java] Optional.of는 왜 있을까?

by dev_jinyeong 2024. 10. 10.

Optional 그리고 Optional.of

Optional은 Java를 사용할 때 null-safe한 코드를 간결하게 짤 수 있게 해주는 좋은 클래스입니다.

저는 가능하면 Java에서 null을 다룰 때 Optional을 많이 사용하는데요.

 

1. if문을 사용한 검사보다 코드가 더 간결하고

2. 직접적으로 null을 사용하지 않아도 되며

3. 람다를 활용하여 필요한 동작을 매끄럽게 연계할 수 있기 때문입니다.

 

그런데 Optional을 사용하다 보니 Optional.of가 왜 필요한지 의문이 들었습니다.

 

Optional.of의 javadoc을 보면 다음과 같이 나와 있는데요.

Returns an Optional describing the given non-null value.

Throws:
NullPointerException - if value is null

 

Optional이 null-safe한 코드를 짜기 위한 클래스인데, NullPointerException이 발생하는 메서드인 부분.

그러므로 Optional.of를 쓰려면, 대상이 non-null임을 확신해야 하는 부분이 의문이었습니다.

 

null도 받을 수 있는 ofNullable이 null인 대상을 다루기에 적절한데, of는 어디다 쓰는것일까요?

일반적인 이야기와 Optional.of의 사용에 대한 고민

이에 대해서 깊이 고민하면서 여러 글을 찾아봤지만, 만족할 만한 답을 얻지 못했습니다.

 

chatGPT에게 물어보거나 블로그 글을 봐도, Optional.of에서 NullPointerException이 일어나면 무조건 non-null인 값에 null이 들어간 것이므로 예상된 제어 흐름에서 벗어나니 익셉션이 발생하는 것으로 예상치 못한 실행을 막을 수 있다는 식의 답변을 많이 볼 수 있었습니다.

 

그렇지만 이건 논리적으로 말이 안 되는 주장입니다.

 

  1. 무조건 non-null인 값에 null이 들어간다면 (non-null이 거짓이라면), 전제를 위반하므로 사실은 무조건 non-null이 아니다.
    1. 만약 non-null이 아니라면 nullable이므로, Optional.ofNullable로 처리하는 게 적절하다.
  2. 만약 무조건 non-null인 값에 null이 들어가지 않는다면 (non-null이 참이라면), 이미 null-safe 하므로 Optional을 사용하는 것은 메모리 낭비이다.
    1. 구조적으로 null 일 수 없는 값 (primitive 타입) 같은 경우 Optional에 들어갈 수 없다.
    2. Optional은 제네릭 타입을 이용하여 구현되어 있기 때문이다.
    3. 제네릭 타입은 런타임에 타입이 무효화되며, Object로 처리된다.
    4. Object와 그를 상속하는 객체 (Java의 모든 객체)는 nullable이기 때문에,  Optional이 받는 제네릭 타입은 모두 nullable이다.
    5. Java에서 구조적으로 null 일 수 없는 primitive 타입은 Optional의 제네릭 타입이 될 수 없기 때문에, Optional의 제네릭 타입은 무조건 nullable이다.
    6. 전제가 참이라면, Optional을 사용하는 것은 무의미하다.
  3. 제어 흐름상 non-null인 객체가 모종의 이유로 null이 될 수 있다고 하더라도, Optional.of로 NullPointerException을 일으키는 것이 Optional.ofNullable로 null-safe하게 처리하는 것보다 나은 코드라는 것은 사실이 아니다.
  4. Optional.of로 NPE를 일으키는 것과, null에 메서드를 실행해서 NPE를 일으키는 것은 질적으로 다를 바 없다.

자바에서는 non-null을 보장할 수 있는 타입은 primitive 타입이고, primitive 타입은 null 검사가 무의미하므로 Optional을 사용할 필요가 없고, 사용할 수도 없습니다.

제어 흐름상 non-null인 객체가 모종의 이유로 null이 될 수 있다고 한다면, 그때야말로 Optional.ofNullable을 사용할 때가 아닐까요?

 

위와 같은 이유로, Optional.of가 일으키는 NPE는 사실 좋은 NPE다... 같은 주장은 말도 안되는 소리입니다.

 

"우리 Optional.of가 일으키는 NPE는 몸에 좋은 NPE에요." 같은 소리죠.

Optional의 구현과 잘 사용하는 방법에 대해서

https://stackoverflow.com/questions/31696485/why-use-optional-of-over-optional-ofnullable

 

이에 대해서 깊이 고민하면서 관련해서 찾아보다가 다음과 같은 스택 오버플로우 글을 찾았습니다.

이 글의 가장 추천을 많이 받은 답변도 Optional.of로 NPE를 일으키는 것이 null을 아예 감지하지 못하는 것보다 낫다는 이야기인데, 그에 대해서는 위에서 논증했습니다.

 

다만 두번째 답변에서 아주 좋은 인사이트를 얻었는데요.

 

Optional이 제공하는 스태틱 메서드는 of, empty, ofNullable 세 가지입니다.

 

하나씩 구현을 살펴보면 다음과 같은데요.

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

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

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

@SuppressWarnings("unchecked")
public static <T> Optional<T> ofNullable(T value) {
        return value == null ? (Optional<T>) EMPTY
                             : new Optional<>(value);
}

 

이 메서드들을 살펴보면 of로 NPE를 일으키는 게 더 좋은 코드라는게 말도 안되는 소리라는 걸 알 수 있습니다.

왜냐면, of의 구현을 보면 Objects.requireNonNull으로 null 검사를 하고 있기 때문입니다.

@ForceInline
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

 

그리고 Objects.requireNonNull의 코드를 보면 if문과 ==으로 null 검사를 하고 있습니다!

그러니까, of로 NPE를 일으키는 코드를 짜는 것은 Optional.ofNullable을 쓰거나 if문으로 null 검사를 하는 것보다 무조건 나쁜 코드인 것이죠.

왜냐면, 어차피 if문으로 null 검사를 해서 NPE를 발생시키기 때문입니다.

if (example == null) {
	throw new NullPointerException();
}

 

사실 of로 NPE를 일으키는 코드는 위와 같은 코드입니다.

null 검사를 해서 적절한 처리를 하는 코드가 위의 코드보다 무조건 낫습니다.

 

스택오버플로우 답변에서 제공하는 인사이트와 Oracle 공식 문서의 내용을 참고해보면 다음과 같습니다.

 

Oracle 공식 문서

API Note:
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.

 

스택오버플로우 답변

What is paired with Optional#of(T value) is Optional#empty().
Use of or empty to create a new Optional instance.
Use ofNullable to wrap an existing variable as Optional.

 

그러니까, Optional 객체는 가장 큰 목적은 함수의 리턴 타입으로 사용되는 것입니다.

함수의 리턴 타입으로 Optional을 사용하게 되면,

  1. 어떤 값이 nullable하다는 것을 명시적으로 나타낼 수 있습니다.
    1. 개발자에게 null-safe한 코드를 짤 것을 강제합니다.
  2. nullable한 값을 null-safe하게 다루기 위한 편리한 메서드를 제공합니다.
    1. 메서드 체이닝과 람다를 이용한 간결하고 효율적인 함수형 프로그래밍 방법을 제공합니다.

반대로 함수의 리턴 타입으로 Optional을 사용하는 입장에서는 주려는 값이 없을 수도, 있을 수도 있습니다.

주는 입장에서는 이 값이 있을지 없을지 무조건 알고 있습니다.

 

그러므로 없으면 Optional.empty, 있으면 Optional.of를 사용해서 줍니다.

이 경우에는 제어 흐름상 자연스럽습니다. 왜냐면 Optional로 래핑하기 전이기 때문에, null인지 아닌지 알고 있기 때문입니다.

 

반대로 이 값을 받아서 사용하는 입장에서는 이 값이 null인지 아닌지 모릅니다.

따라서 Optional의 isPresent와 같은 메서드를 이용해서 null-safe하게 처리합니다.

 

그러면 Optional.ofNullable은 언제 사용할까요?

외부에서 값이 주어졌는데, nullable인데 Optional로 감싸져 있지 않은 경우입니다.

네트워크에서 들어온 값, DB 트랜잭션으로 들어온 값, OS 변수 값 등은 충분히 없을 수도 있겠죠.

 

그러면 이 값들은 Optional.ofNullable로 받아서 isPresent와 같은 메서드를 이용해서 null-safe하게 처리하면 됩니다.

결국 ofNullable은 삼항 연산자를 통해서 null 검사를 하고 있기 때문에, if문을 이용해서 null 검사하는 로직을 편리하게 제공하고 있습니다.

 

결론

  1. null일 수 있는 값을 리턴하는 입장에서는 리턴 타입을 Optional로 정하고 null 여부에 따라 Optional.empty, Optional.of를 이용해서 리턴하자.
  2. 외부에서 들어온 nullable한 값은 Optional.ofNullable로 null-safe하게 처리하자