Optional Class 사용하기

Yennie·2024년 5월 10일

JPA

목록 보기
7/19

JPA의 findById 메서드 리턴 타입인 Optional 클래스는 Java 8에 추가된 새로운 API로 null 처리를 잘 다룰 수 있게 도와준다.

Optional Class란? 언제 사용하는가?

  • 'Null일 수도 있는 객체'를 감싸는 일종의 Wrapper 클래스로, 여러 가지 API를 제공하여 null일 수도 있는 객체를 다룰 수 있도록 한다. null의 사용이 오류를 발생시킬 수 있으므로 (nullPointerException) "결과 없음"을 명확히 표현할 방법이 필요한 라이브러리 메서드 반환 타입에 한정된 메커니즘을 제공하는 것이 목적이다.

Optional의 장점과 단점

장점

  • 명시적으로 변수에 대한 null 가능성을 표현하고 체크를 직접하지 않아도 된다.
  • NullPointerException이 발생할 가능성이 있는 값을 직접 다룰 필요가 없다.

단점

  • NullPointerException 대신 NoSuchElementException이 발생
  • 코드의 가독성이 떨어짐
  • 시간적, 공간적 비용이 증가. Optional은 객체를 감싸는 컨테이너이기 때문에 Optional 객체 자체를 저장하기 위한 메모리가 추가로 필요하며, 그 안에 있는 객체를 얻기 위해서는 Optional 객체를 통해 접근해야 하기 때문에 접근 비용이 든다.
  • 직렬화 (serialize)를 지원하지 않기 때문에 캐시나 메세지큐 등과 연동 시 문제가 발생할 수 있다. Serializable 인터페이스를 구현하지 않음 -> 클래스의 인스턴스 필드로 사용해선 안됨

Optional 사용방법

1. Optional 변수에 null 할당 금지

잘못된 예
Optional<Person> findById(Long id) {
    // find person from db
    if (result == 0) {
        return null;
    }
}


옳은 예 
Optional<Person> findById(Long id) {
    // find person from db
    if (result == 0) {
        return Optional.empty();
    }
}

Optional은 내부 값을 null로 초기화한 싱글턴 객체를 Optional.empty() 메서드를 통해 제공하고 있고, "결과 없음"을 표현해야하는 경우라면 null 대신 Optional.empty()를 반환하여 적절하게 처리할 수 있다.

2. Optional.get() 호출 전에는 Optional 객체가 값을 가지고 있는지 여부를 확인

빈 Optional 객체에 get() 메서드를 호출하는 경우 NoSuchElementException이 발생한다.

옳은 예 
Person person = findById(4).orElseThrow(PersonNotFoundException::new);
String name = person.getName();

잘못된 예 
Optional<Person> maybePerson = findById(4);
String name = maybePerson.get().getName();

3. 값이 없는 경우 Optional.orElse()를 통해 이미 생성된 기본 값(객체)를 제공

  • Optional.isPresent() 메서드를 통해서는 Optional 객체에 값이 있는지 확인이 가능
  • Optional.orElse() 메서드는 "기본값 반환"에 해당하는 메서드로, Optional 객체의 값이 없는 경우 orElse 인자로 명시된 값을 대신 반환
Person person = findById(4).orElse(UNKNOWN_PERSON);

4. 값이 없는 경우, Optional.orElseGet()을 통해 이를 나타내는 객체를 제공할 것

문자열처럼 동일한 객체 참조를 반환해도 괜찮은 경우에 적합
orElse는 기본값으로 반환할 값을 인자로 받는 것
orElseGet()은 값이 없는 경우 이를 대신해 반환할 값을 생성하는 람다를 인자로 받음
-> 값이 없는 경우 매번 새로운 객체를 반환하는 경우에는 orElseGet()를 사용할 수 있음

findById(4).orElseGet(() -> new Person("UNKNOWN")); 

5. 값이 없는 경우에는 Optional.orElseThros()를 통해 명시적으로 예외를 던지기

값이 없는 경우, 기본값을 반환하는 대신 예외를 던져야하는 경우 Optional.orElseThrow()를 사용

findById(4).orElseThrow(() -> new NoSuchElementException("Person not found"));

6. 값이 있는 경우에 이를 사용하고 없는 경우에 아무 동작도 하지 않는다면 Optional.ifPresent()를 활용하기

findById(4).ifPresent((user) -> System.out.println(user.getName()));

7. ifPresent-get은 orElse나 orElseXXX 등으로 대체

나쁜 예: 
Optional<Person> maybePerson = findById(4);
if (maybePerson.isPresent()) {
    Person person = maybePerson.get();
    System.out.println(person.getName());
} else {
    throw new NoPersonFoundException("No person found id maches: " + 4);
}

좋은 예: 
Person person = findById(4)
    .orElseThrow(() -> new NoPersonFoundException("No person found id maches: " + 4));
System.out.println(person.getName());

8. Optional은 필드 타입으로 사용하지 않기

반환을 위해 설계된 타입으로 메서드의 인자로 사용하거나 클래스의 필드로 선언하지 않아야함

9. Optional을 빈 컬렉션이나 배열을 반환하는데 사용하지 않아야함

그냥 빈 컬렉션 또는 배열을 반환하자

10. Optional의 컬렉션을 사용하지 않기

Optional은 불변 객체로, 컬렉션을 사용하게 되면 메모리 문제를 일으키는 원인이 될 수 있다.

11. 원시타입의 Optional에는 OptionalInt, OptionalLong, OptionalDouble 사용하기

12. 내부 값의 비교에는 Optional.equals를 사용하기

13. 변환에 map과 flatMap사용을 고려하기

스트림처럼 함수형 스타일로 코드 작성이 가능해짐

Address getUserAddress(long id) {
  findById(id)
      .map(Person::getAddress)
      .map(Address::of)
      .orElseGet(Address::emptyAddress());
}

14. 값에 대해 미리 정의된 규칙이 있는 경우 filter 사용하기

스트림과 동일하게 값을 필터링하는 역할을 해서, 참인 경우에는 Optional이 반환되고, 그렇지 않은 경우에는 비어 있는 Optional을 반환

boolean isValidName(String username) {
  return Optional.ofNullable(username)
    .filter(this::isIncludeSpace)
    .filter(this::isOverLength)
    .filter(this::isDuplicate)
    .isPresent();
}

참고
https://www.latera.kr/blog/2019-07-02-effective-optional/
https://mangkyu.tistory.com/203

profile
PM | Aspiring SWE | linkedin.com/in/emilyyeeun

0개의 댓글