글 작성자: 개발섭

Java는 Null safety가 중요하다

거두절미하고 이야기하겠다. 당연히 나는 이 코드가 동작할거라 생각했다.

names.stream().filter(Objects::nonNull).forEach(System.out::println);

근데 NPE가 발생한다. 응? 당연히 잘되는게 아닌가 싶지만...
우리는 names가 List 타입인거 까지는 아는데.... 그게 어떤 값일지 모르기 때문에 발생하는 문제이다. 만약 names가 null이라면? → 이때 NPE를 발생하는 것이다.

심지어 이건 JPA에서도 발생하는 문제인데..

이런 방법을 해결하기 위한 방법?

간단하다. Optional로 Wrapping하는 거다.

Optional.ofNullable(names).orElseGet(ArrayList::new)
    .stream()
    .filter(Objects::nonNull)
    .forEach(System.out::println);

더 좀더 Stream을 조금 더 ElseGet을 활용하기 위해서는 이렇게 써도 좋다.

Optional.ofNullable(names).orElseGet(Collections::emptyList)
    .stream()

근데 문제는 이게... Stream 사용할때 매번 이렇게 Optional을 감싸서 쓰는건 매우 귀찮은 일이다.
그럼 함수를 아예 만들어서 사용하자.

public static <T> Stream<T> nullSafeStream(final Collection<T> collection) {
    return Optional.ofNullable(collection)
            .orElseGet(Collections::emptyList).stream();
}

이런식으로 방어용 Stream을 만들어쓰면 NPE 발생을 막을 수 있다.

왜 사용해야하는가?

이게 Compile Error로 문제가 터지는게 아니다. 일단 List자체가 null인 경우에 발생하는 것이기 때문에, 무조건 Runtime Exception이 발생한다. 즉, 내가 null인 상황을 확실히 컨트롤 할 수 있다면야.. 문제가 없다고 보지만...
실무 상황에서 솔직히 null이 안 발생하냐 발생하냐를 완전히 컨트롤 하긴 쉽지 않을거라고 본다. 레거시 코드를 죄다 null이 불가능하도록 코드를 고치거나 해야하는데.. 이건 좀 쉬운 일은 아닐거라 본다. 레거시 코드를 함부로 막 고치다가 엄한곳에서 이상한 오류가 발생하던 경험이 몇번 있어서, 차라리 방어 코드를 설치하는게 좋다고 생각한다.

후일담

JPA에서는 어떤식으로 처리해야지 다음과 같은 상황을 막을 수 있을까 조금씩 수정해서 글좀 작성해보겠다.

출처

https://sigmasabjil.tistory.com/43
https://www.baeldung.com/java-null-safe-streams-from-collections
https://stackoverflow.com/questions/41590134/null-safe-collection-as-stream-in-java-8
http://gist.githubusercontent.com/felipebelluco/defa683a7f9d437488d3a46e72665af2/raw/38ef1aec18eb845aab49023c05c77f59abc0c37f/Main.java