Java에서 Null 체크하기

2023년 9월 10일 작성

JavaOptional

나는 원래 명시적으로 null 체크를 해주는 것을 좋아했었다. null 체크 코드는, 오히려 다른 API를 사용하는 것보다 직관적이라고 생각했기 때문이다.

그런데 Optional 이나 Objects가 제공하는 API들이 눈에 익으니, 그게 더 가독성이 좋아보이기도 하고 코드에 depth가 생기지 않는것도 좋아보였다. 별것아니라고 생각해서 상황에 따라 한 1-2분정도 고민하다가 가독성 좋아보이는 걸로 고르곤 했다.

그렇게, 나는 납득이갈만한 기준없이 그날 기분에 따라 null 체크 방식을 달리하게 되었다. 사실 사용기준을 딱 세워둔다고 해서 모든 상황에 적용할 수 있는 것은 아니다.

선택이 필요한 상황마다 적합한 방식을 고려할 수 있기 위해 각 null 체크 방식이 왜 생겨났는지를 알아보려고한다.

나는 이 질문과 답변을 참고해서 정리해봤다.

java.util.Objects vs Optional which is preferable?

Objects 클래스

Objects 클래스는 jdk 1.7 에 출시되었다. 계속 발전 중인데, 특히 java 9에서 새로운 API들이 제공되는 등의 변화가 있었다. (https://www.baeldung.com/java-9-objects-new)

  • requireNonNull()

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

    public static boolean isNull(Object obj) {
        return obj == null;
    }

반대 버전으로 nonNull 도 있음

  • requireNonNullElse (이런것도 있군)

    public static <T> T requireNonNullElse(T obj, T defaultObj) {
        return (obj != null) ? obj : requireNonNull(defaultObj, "defaultObj");
    }

Optional.ofNullable(obj).orElse(defaultObj) 랑 비슷한데, 언제 무엇을 사용해야할까? →

Optional

결론부터 말하자면, Optional은 null이 반환되는 것으로 인한 side effect를 줄이기 위한 방법으로 도입됐다.

하지만 존재 자체가 논란이 많은 기능이었다.

Some people are happy to create an object for the sake of being able to do this. Although sometimes less happy when they realize that Optionalism then starts propagating through their designs, leading to Set<Optional<T>>s and so on.

Optional 도입 제안을 한 Doug Lea의 유일한 걱정이었다. Optional을 만들게 되면 이를 오용할 수 있게 된다는 것. 이 맥락에서 토론은 계속 이어졌고, 합의는 Optional을 최소한으로 사용하는 것으로 이뤄졌다.

Optional should be (and currently is) a very limited abstraction, one that is only good for holding a potential result, testing for its presence, retrieving it if it is present, and providing an alternative if not. We should resist the temptation to make it into something more or make it into a knock-off of the similar Scala type.

Optional은 potential 결과를 감싸고, 그 결과의 존재를 확인하고 존재한다면 다시 접근하는 ‘if not’ 의 대안을 제공하는 것에만 적합한 굉장히 제한된 추상화기법이어야한다. 따라서 우리는 Optional을 다른 것에 집어넣거나 Scala의 Option 타입처럼 사용하려는 유혹을 막아야한다.

ㅋㅋㅋ 나는 Optional 탄생 이유와는 달리 그냥 막 썼다.

비교

Objects.requireNonNullElse(obj, defualt) vs Optional.ofNullable(obj).orElse(default)

Objects.requireNonNullElse(T obj, T defualt)

이 메서드는 말그대로, 반드시 null 이 아닌 obj를 호출해야한다는 것을 명시하는 메서드다.

public Rectangle(int width, int height) {
		this.width = requireNonNullElse(width, 100);
    this.height = requireNonNullElse(height, 100);
}

그리고 코드를 읽는 사람은 이 메서드의 반환값은 항상 null 이 아니라고 인식하게 될 것이다.

용례

  • 생성자, 메서드 인자 검증
  • null을 허용하지 않는 경우

Optional.ofNullable(obj).orElse(default)

옵셔널의 API를 논하기 위해서는 옵셔널이 무엇을 위해 만들어졌는지 다시 확인해야한다.

Optional은 타입 시스템에서 null이 있을 수 있음을 그저 표현하는 방법이며, if-null 검사의 대안으로 사용되는 것은 아니다.

결론

나는 앞으로 Optional 사용을 .. 자제해보려고 한다. 오직 return을 위해서. 내 api 사용자를 위해서 써보도록 하겠다. 꼭 그래야하는 것은 아니지만 Optional 사용으로 불필요한 wrapping을 하는 것이 예술을 위함 그 이상도 이하도 아닌 경우가 많은 것 같다. 난 개발자니까 예술보단 효율과 단순함을 택하겠다.

Objects

  • 생성자/메서드 인자 null 확인

Optional

  • 스트림 연산할 때
  • 없을 수 있는 값 반환할 때

아니면 좀 더 신중하자

Optional을 사용하는 것에 신중하고 선택의 이유를 항상 생각해보려 하면 좋을 것 같다.


중간에 Optional 제안에 대한 글을 읽으면서

prevents people from distinguishing between a stream that is empty and a stream containing only the "orElse" value. Just like Map.get() prevents distinguishing between "not there" and "mapped to null.”

를 봤다. Map.get()은 원래 not there 과 mapped to null 이 구분되지 않는 것이 문제였구나

되게 긴글들을 많이 봤는데 꽤나 단순한 결론이 나서 조금 허무하지만? 그래도 생각해볼 수 있어서 재미있었다.