Optional
Programming/Java

Optional

서론

면접 질문으로 Optional에 대한 꼬리질문을 받았다.

  1. 옵셔널에 대한 소개와 이점
    1. null을 그냥 반환하면 안되나요?
    2. 옵셔널 처리 방법?
    3. 옵셔널을 사용하면서 주의해야할 점은?
    4. 인텔리제이 옵셔널을 파라미터로 넣으면 경고가 뜨는데 왜그럴까?

꼬리 질문에 대해 제대로 답변하지 못했기에 Optional을 다시 공부하는 마음가짐으로 이 글을 작성하게 되었다.


NullPointerException 피하기

NullPointerException는 골치 아픈 문제이다. 1965년 처음으로 null을 도입한 토니 호어(Tony Hoare)도 null을 만들었을 때를 회상하며, 이는 10억 달러짜리 실수라고 했다. null 때문에 발생할 수 있는 문제는 아래 예제를 통해 살펴보자.

public class Person {
    private Car car;
    public Car getCar() { return car; }
}
public class Car {
    private Insurance insurance;
    public Insurance getInsurance() { return insurance; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}

만약 Person이 갖고 있는 자동차보험의 이름을 알고 싶을 때를 생각해보자. 이 때 예기치않은 NPE을 피하려면 다음과 같이 코드를 구현해야 할 것이다.

public String getCarInsuranceName(Person person) {
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance insurance = car.getInsurance();
            if (insurance != null) {
                return insurance.getName();
            }
        }
    }
    return "Unknown"; // 참조하려는 객체가 null인 경우 "Unknown" 반환
}

변수를 참조할 때마다 null일 가능성이 존재하기 때문에 중첩된 if문을 통해 null인지 확인해야 한다. if문을 반복하다 보면 코드의 구조가 엉망이 되고 가독성도 떨어진다. 이러한 null 참조 문제를 해결하기 위해 나온 것이 자바8의 java.util.Optional<T> 클래스이다.

Optional이란

Optional은 null이 될 수도 있는 객체를 포장하는 Wrapper 클래스이다. 

https://docs.oracle.com/javase/9/docs/api/java/util/Optional.html 에서는 API Note로 Optional의 사용목적을 설명하고 있다.

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.

메서드가 반환할 결과값‘없음’을 명백하게 표현할 필요가 있고, null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional을 사용하자는 것이 Optional을 만든 주된 목적이다.Optional 타입의 변수의 값은 절대 null이어서는 안 되며, 항상 Optional 인스턴스를 가리켜야 한다.

Optional로 감싸 반환하게 되면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해 간단히 처리할 수 있다. 이를 통해 NPE가 발생하지 않는 간결하고 안전한 코드를 작성하는 것이 가능해진다.

 

모든 값을 Optional로 감싸야만 하는 것은 아니다. 값이 반드시 이름을 가져야 한다면 Optional로 감싸지 않아야 한다. 아래 보험회사 이름을 Optional이 아닌 String으로 선언된 것도, 보험회사는 반드시 이름을 가져야하기 때문이다. 만약 보험회사의 이름을 null이어서 NPE가 발생했다면 예외처리하는 코드를 추가할 것이 아니라 왜 이름이 없었는지를 밝혀야 할 것이다. Optional로 이것을 감싸는 것은 에러를 추적하기 어렵게 할 뿐만 아니라, 에러를 숨기는 꼴이 된다.

public class Person {
    private Optional<Car> car;
    public Optional<Car> getCar() { return car; }
}
public class Car {
    private Optional<Insurance> insurance;
    public Optional<Insurance> getName() { return name; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}

Optional 클래스 생성

Optional 객체를 생성할 때는 of() 또는 ofNullable()을 사용한다. 참조변수의 값이 null일 수 있다면 of() 대신 ofNullable()을 사용해야한다. of()는 매개변수의 값이 null이면 NPE가 발생한다.

String value = null;
Optional<String> optional = Optional.of(value); // NPE 발생

Optional을 사용하면서 주의할 점

위 예제에서 getter의 반환 값으로 Optioanl을 사용했다. Java 언어 아키텍트인 Brian Goetz는 게터(getter)의 반환값으로 Optional을 사용하는 것은 과용(over-use)라고 말했다. Stackoverflow에 작성한 답변(링크)은 여기서 찾아볼 수 있다. Brian Goetz 답변을 간단히 번역하면 다음과 같다.

당연히 사람들은 맘대로 할 겁니다. 하지만 우리는 명확한 의도를 가지고 이 기능(Optional)을 추가했고, 그건 많은 사람들이 바랬던 일반적인 목적의 maybe 타입은 아니었습니다. 우리 의도는 “결과 없음”을 명확히 표현할 방법이 필요한 라이브러리 메서드 반환 타입에 한정된 메커니즘을 제공하는 것이었으며, 이러한 경우에 null의 사용이 오류를 발생시킬 가능성이 압도적으로 높았기 때문입니다.

예를 들어, 결과의 배열이나 리스트를 반환하는 곳에는 사용하지 않는 것이 좋을 지도 모릅니다. 대신 빈 배열이나 리스트를 반환하세요. 필드나 메서드 파라미터로는 거의 사용해서는 안됩니다.

일상적으로 이를 게터(getter)의 반환 값으로 사용하는 것은 분명히 과용(over-use)이라고 생각합니다.

피해야 하는 Optional에는 아무 잘못이 없습니다. 그저 많은 사람들이 원했던 것이 아닐 뿐이며, 우리는 과용했을 때의 위험성에 대해서도 염려했습니다.

Optional이 만들어진 목적을 이해하고 올바르게 쓰는 것이 중요하다. Optional을 올바르게 쓰는 방법에 대해서는 다른 사람들이 작성한 좋은 글이 많으니 링크로 대신한다.

https://www.latera.kr/blog/2019-07-02-effective-optional/

인텔리제이에서 Optional을 파라미터 또는 필드로 사용하면 경고가 뜨는 이유

인텔리제이에서 Optional을 파라미터 또는 필드로 사용하면 경고가 뜬다.

Inspection info: Reports any cases in which java.util.Optional<T>, java.util.OptionalDouble, java.util.OptionalInt, java.util.OptionalLong, or com.google.common.base.Optional are used as types for fields or parameters.
Optional was designed to provide a limited mechanism for library method return types in which a clear way to represent "no result" was needed.
Using a field with the java.util.Optional type is also problematic if the class needs to be Serializable, as java.util.Optional is not serializable.

경고메시지를 해석하면 다음과 같다.

  • Optional은 반환 타입에 대해 "결과 없음"을 나타내기 위해 제한적으로 설계되었다.
  • Optional을 필드 타입으로 사용하면 직렬화(serializable)할 때 문제가 된다.

즉, Optional을 원래 목적인 반환타입으로 사용하지 않았기 때문에 직렬화 할 때 문제가 될 수 있다고 인텔리제이에서 알려주는 것이다. Optional을 반환타입에만 사용하도록 하자.


참고 자료

'Programming > Java' 카테고리의 다른 글

package-info.java란  (0) 2023.07.02
JPA Entity의 equals와 hashCode  (0) 2022.08.08
컨트롤러는 상태를 가지면 안된다.  (0) 2022.03.03
자바 추상클래스와 인터페이스  (0) 2021.10.02