본문 바로가기

[Java] Optional이 필요한 이유

@xuv22025. 8. 9. 11:58

미친 Null의 역습

세상에서 가장 무서운 예외가 있다.

그건 바로 객체가 제대로 전달되지 않아 null.메서드() 형태로 코드 흐름을 가져가버리는 NullPointerException이다.

 

자바에서 null == 값이 비었다를 의미하는데, 존재하지 않는 값에 대고 메서드를 호출하면 null.XX() 형태로 코드가 만들어지기에 프로그램이 예기치 않게 종료될 수 있다.

만약 다음과 같이 코드를 작성했다고 했을때를 가정해보자.

아직 null은 아니지만 언젠가는 null이 될 객체.method1().method2().method3()....methodN();

시작은 null이 아니더라도, 중간에 널포인터예외가 발생하면 메서드 체이닝 중 어느 메서드에서 발생한 버그인지 찾기가 쉽지가 않다.

또한 모든 메서드별로 if(obj != null) ... else... 와 같은 널 체크 로직 코드를 작성하기엔 코드가 복잡해지고 가독성이 떨어진다.

 

더더욱 옵셔널이 필요한 주요한 이유는 메서드의 의도성이라는 점이다. 애초에 일반 raw타입 메서드 시그니처를 보고서는 해당 메서드가 null을 반환할 수도 있다라는 점을 한번에 이해하기 어렵다.


이를 해결할 Optional

자바 8버전부터 람다와 함께 도입된 문법으로써, 값이 있을 수도 있고 없을 수도 있다는 점을 명시적으로 표현하여 메서듸의 의도성을 명확히 하고자 등장한 개념이다.

Optional은 일종의 겉 포장지로써 null 대신 빈값을 Optional.empty()로 넘겨 의도를 명확히 드러낼 수 있다.

Optional을 사용하면 null 체크 로직을 단순화하고, 빌드를 할때 빠르게 NPE가 발생하는 지점을 파악할 수 있다.

먼저 Null 을 넘기는 예시를 보자.

package optional;

import java.util.HashMap;
import java.util.Map;

public class OptionalStartMain1 {

    private static final Map<Long, String> map = new HashMap<>();

    static {
        map.put(1L, "Kim");
        map.put(2L, "Seo");
        // 3L은 넣지 않아서 찾을 수 없는 ID로 활용
    }

    public static void main(String[] args) {
        findAndPrint(1L); // 값이 있는 경우
        findAndPrint(3L); // 값이 없는 경우
    }

    // 이름이 있으면 이름을 대문자로 출력 ,없으면 "UNKNOWN" 을 출력하라
    static void findAndPrint(Long id) {
        String name = findNameById(id);

        // 1. NullPointerException 유발
        //System.out.println("name = " + name.toUpperCase());

        // 2. if문을 활용한 null 체크
        if (name != null) {
            System.out.println(id + ": " + name.toUpperCase());
        } else {
            System.out.println(id + ": " + "UNKNOWN");
        }
    }

    static String findNameById(Long id) {
        return map.get(id);
    }
}

3L은 값이 없기 때문에, null -> Unknown으로 출력하도록 했다.

이 코드의 문제점은 findNameById 메서드는 null을 반환할 수도 있다는 점이다. 하지만 코드만 봤을 때는 이 메서드가 null이 반환 될 수 있다는 점을 알기 쉽지 않다.


Optional로 반환해보기

package optional;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class OptionalStartMain2 {

    private static final Map<Long, String> map = new HashMap<>();

    static {
        map.put(1L, "Kim");
        map.put(2L, "Seo");
        // 3L은 넣지 않아서 찾을 수 없는 ID로 활용
    }

    public static void main(String[] args) {
        findAndPrint(1L); // 값이 있는 경우
        findAndPrint(3L); // 값이 없는 경우
    }

    // 이름이 있으면 이름을 대문자로 출력 ,없으면 "UNKNOWN" 을 출력하라
    static void findAndPrint(Long id) {
        Optional<String> optName = findNameById(id);
        String name = optName.orElse("UNKNOWN");
        System.out.println(id + ": " + name.toUpperCase( ));
    }

    static Optional<String> findNameById(Long id) {
        String findName = map.get(id);
        Optional<String> optName = Optional.ofNullable(findName);
        return optName;
    }
}

옵셔널은 일종의 겉 포장지라고 했다. 이번에는 String 을 Optional의 제네릭으로 감싸서 반환해보자.

findNameById는 이제 옵셔널을 반환하고, ofNullAble 메서드와 함께 이 값은 null일 수 있음을 명시하고 반환하도록 한다.

옵셔널을 도입하게 되면 위 문장에서 보듯 큰 장점이 있는데, 해당 메서드가 null을 반환할 수도 있다는 사실을 명시적으로 표현할 수 있게 되었다.

그 다음 findAndPrint 메서드는 orElse 메서드를 통해 값이 없으면 UNKNOWN이라는 대체값을 반환하도록 설정하였다.

 

이 방식을 통해 값이 없을 수도 있다는 점을 호출하는 측에 명확하게 전달할 수 있게 되었으므로 null 체크를 강제하여 코드의 명확성 + 안정성을 높일 수 있게 되었다.

 

결과는 이전과 같으므로 생략한다.

xuv2
@xuv2 :: xuvlog

폭싹 늙었수다

목차