Java Optional 제대로 사용하기

이상민·2022년 2월 17일
3
post-thumbnail

Java의 Optional

  • 자바 8에서 추가된 래퍼 클래스로 결과 값이 없을 수 있음을 나타내고, 이를 확인하고 처리하기 위한 api를 제공한다
Optional<SomeObject> someMethod() {
   ...
   return Optional.ofNullable(someObject);
}

// 클라이언트 코드
SomeObject someObject = someMethod()
    .orElseThrow(() -> new NullPointerException("객체가 null!");
  • Optional을 만든 Brian Goetz는 그 의도를 다음처럼 설명한다

... we did have a clear intention when adding this feature, and it was not to be a general purpose Maybe type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

  • 오늘은 Optional을 의도대로 잘 사용하는 방법과, 주의사항을 정리한다!

1. 필드나 메소드 패러미터에 사용하지 마라

  • 위에서 언급한 것처럼 Optional은 의도를 가지고 만들어졌다. 필드나 메소드에 이를 사용하는 것은 과용이다. 메소드 반환 타입으로만 사용하도록 하자

  • 추가적으로 필드에 Optional 사용시 직렬화가 되지 않는다

// Bad!
class SomeClass {
    private Optional<String> sth;
    
    String someMethod(Optional<String> param) {
        ...
    }
}

// Good!
class OtherClass {
    private String oth;
    
    Optional<String> otherMethod(String param) {
        ...
    }
}

2. 검증하지 않고 get()하지 마라

  • Optional 타입이라는 것은 감싸진 객체가 null일 수 있다는 것이다. 너무나도 당연하고 IDE가 아마 경고까지 해줄테지만, 객체가 있는지 확인하지 않고 get()을 호출하지 말자
// Bad
Optional<String> optText = getText();
String text = optText.get();

// Good
Optional<String> optText = getText();
String text = optText.orElseThrow(() -> new NullPointerException(""));
String text2 = optText.orElse("");
... 등등등 다양한 Optional api를 활용하자 
  • 확인하지 않는다면 애초에 Optional을 사용할 이유가 없다. Brian Goetz는 get() 메소드 이름을 지은 것을 후회한다며 다음과 같이 말했다

NEVER call Optional.get unless you can prove it will never be null; instead use one of the safe methods like orElse or ifPresent. In retrospect, we should have called get something like getOrElseThrowNoSuchElementException or something that made it far clearer that this was a highly dangerous method that undermined the whole purpose of Optional in the first place.


3. 단순 Null 체크를 위해 사용하지 마라

  • 다음과 같이 Optional을 단순 null 체크를 위해 사용하는 코드를 본적이 있다
public void someMethod(SomeObject object) {
    Optional.ofNullable(object).orElseThrow(() -> new NullPointerException());
    ...
}
  • 위 코드는 Optional의 의도와 다르게 사용하고 있을 뿐만 아니라, null이 아닌 경우 객체를 반환함에도 이를 사용하지 않고 있다

  • 위 같은 상황에서는 그냥 명시적으로 null 체크를 하자

public void someMethod(SomeObject object) {
    if (object == null) {
        throw new NullPointerException();
    }
    ...
}
  • 만약 이런 보일러플레이트가 싫다면 자동으로 생성해주는 Lombok의 @NonNull을 사용할 수도 있다
public void someMethod(@NonNull SomeObject object) {
    ...
}

4. API를 적극 활용하라

  • 기왕 Optional을 사용하는거 최대한 api를 활용해 가독성을 높이자. Optional은 정말 다양한 API를 제공한다
Optional<Object> optObject;
Object object;

// Bad
if (optObject.isPresent()) {
    object = optObject.get();
} else {
    throw new NullPointerException("null!");
}

// Good
// 참고로 orElseThrow는 자바 10부터 추가됐다 
object = optObject.orElseThrow(() -> new NullPointerException("null!"));
  • filter(), map()과 같은 api는 사용하는 것을 잘 보지 못했다. 한번 사용해보자!

5. 배열이나 컬렉션을 감싸지 마라

  • Optional은 결과가 없음을 나타낸다. 배열과 컬렉션은 내용 없는 객체를 반환할 수 있으므로 Optional을 사용할 이유가 없다.
// Bad
Optional<List<Integer>> method1() ...
Optional<int[]> method2() ...

// Good
List<Integer> method1() {
    ...
    return Collections.emptyList();
}

Optional<int[]> method2() {
    ...
    return new int[0];
}

6. of()와 ofNullable()을 구분하라

  • 메소드명에서 알 수 있듯이 ofNullable()은 null이 들어갈 수 있다. 반대로 of()에는 null이 들어가면 NPE가 발생한다

  • 만약 of()에서 NPE가 발생했다면, 뭔가 의도치 않은 잘못된 동작이 실행 됐음을 알 수 있다


7. Optional<T>를 피할 수 있으면 피해라

  • 객체를 감싸는 Optional<T>이외에도 프리미티브 값을 감쌀 수 있는 OptionalInt, OptionalDouble 등이 존재한다

  • 객체로의 박싱을 피하기 위해 위 객체들을 사용하자

// 특히 값을 이용해 연산을 하는 등 박싱/언박싱이 많이 발생하는 상황에서 사용하면 성능에 나쁘다

// Bad
Optional<Integer> optNum;

// Good
OptionalInt optNum;

8. 동등성 비교를 위해 객체를 꺼내지 마라

  • Optional의 equals()는 1) 동일한 객체, 2) 비어있는 Optional, 3) 감싼 객체의 equals로 비교시 동등한 경우 동등하다고 판단하도록 오버라이딩 되어있다

  • 따라서 감싸진 객체의 비교를 위해 Optional에서 꺼낼 필요가 없다
// Bad
Optional<SomeObject> someObj;
Optional<OtherObject> otherObj;
assertEquals(someObj.get(), otherObj.get());

// Good
Optional<SomeObject> someObj;
Optional<OtherObject> otherObj;
assertEquals(someObj, otherObj);

참고자료

https://homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/

https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555

https://dzone.com/articles/java-8-optional-how-use-it

https://dzone.com/articles/using-optional-correctly-is-not-optional

profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글