Java

[Effective Java] 아이템 55 : 옵셔널 반환은 신중히 하라

jwKim96 2023. 3. 25. 20:52

0. 들어가며

Java 8 이전에는 값을 반환할 수 있을 수도 없을 수도 있는 경우에 정말 애매했습니다.

개발자가 선택할 수 있는건 null 반환, 예외 발생 두 가지 중 하나였는데요.

null을 반환하는 경우에는 호출하는 쪽에서 null 방어코드를 추가해줘야합니다.

만약 빠트리게되면 어디선가 NullPointerException이 발생하죠.

그리고 예외를 발생시키더라도 StackTrace 전체를 추적하기 때문에 비용도 만만치 않다고 합니다.

하지만 이 문제는 Java 8에서 Optional이라는 클래스가 등장하며 해소되었죠.

1. Optional

Java 8 부터 Optional이 등장하며 값을 반환할 수 있을 수도 없을 수도 있는 경우를 대응할 수 있게 되었습니다.

값이 있는 경우에는 해당 객체를 담은 Optional을 반환하고, 없는 경우에는 빈 Optional을 반환합니다.

return Optional.of(something);

return Optional.empty();

그리고 사용하는 쪽에서는 다음과 같은 코드로 값을 확인하고 가져올 수 있습니다.

Optional.isPresent(); // 값이 있는지 확인
Optional.get(); // 값을 가져옴(이 방법은 권장되지 않음)

2. 값을 가져오는 방법

Optional로 부터 값을 가져오는 방법은 여러가지가 있는데요.

그중 가장 많이 쓰이는 방법은 다음과 같습니다.

// 없는 경우 예외를 던지는 방법
Optional.orElseThrow(() -> new IllegalArgumentException("..."));

// 없는 경우 default 값을 지정하는 방법
Optional.orElse("1");

// 없는 경우 다른 값을 가져오는 로직을 호출하는 방법
Optional.orElseGet(() -> getSomething());

이렇게 세가지 방법이 있는데요.

이 방법들 중에서 주의해야할 방법이 있습니다.

바로 orElse 입니다.

2.1 orElse vs orElseGet

둘은 얼핏 보면 거의 똑같아보일 수 있지만, 동작에는 큰 차이가 있습니다.

아래 예제 코드를 통해 조금 더 자세히 알아보겠습니다.

public Something getSomething() {
        // select query to database
        ...
        return something;
}

// orElse
Optional.orElse(getSomething());

// orElseGet
Optional.orElseGet(() -> getSomething());

optional에 값이 없을 경우에는 DB에서 조회하는 형식인데요.

orElse의 경우가 문제가 됩니다.

orElse는 Optional이 비어있든 아니든 호출이 되는데요.

그래서 getSomething() 메서드가 호출되고 쿼리가 나가고 값을 가져옵니다.

하지만 Optional에 값이 들어있으면 이 값은 쓰이지 않고 버려지게 되죠.

하지만, orElseGet의 경우는 다른데요.

orElseGet은 인자로 Supplier를 넘겨받고, 만약 Optional이 비어있을 경우 이를 호출합니다.

그래서 이 경우에는 값이 들어있으면 getSomething 메서드는 호출되지 않습니다.

3. Optional을 쓰지 말아야 할 때

반환값으로 Optional을 사용하는게 항상 좋은건 아닙니다.

Collection, Stream, Array, Optional 을 반환할때 Optional로 감싸지 않아도 됩니다.

예를 들어 Optional<List> 를 반환하는 것 보다는, 빈 List를 반환하는것이 좋습니다.

그리고 Optional로 감싸서 반환한다는건 결국 감싸면서, 다시 풀면서(값을 가져오며) 오버헤드가 발생합니다.

그래서 성능상 중요한 상황에서는 사용하지 않는게 좋습니다.

그리고 박싱된 기본 타입을 담는 경우도 주의해야 하는데요.

int 값을 담는 경우에는 int → Integer → Optional 이렇게 담는다면 2번 감싸지게 되기 때문입니다.

그래서 Java에서는 OptionalInt, OptionalLong, OptionalDouble 같은 클래스를 제공하니, 필요한 경우 이를 사용하도록 합시다.