Java8 Optional이란?

19100 단어 optionaloptional

1. Optional

1-1. 탄생 배경 - 왜?

  • 자바8 이전에는 메서드가 값을 반환할 수 없는 상황인경우 두 가지 선택지가 존재했다.

    1. 예외 던지기
    2. null 반환하기
  • 예외를 던지는 경우에는 스택 추적 전체를 캡처하는 비용문제가 있다.

  • null을 반환하는 경우에는 메서드를 호출하는 곳에서 null처리 코드를 추가해야 하고
    만약 무시하면 언제 어디서 NPE를 만날지 모른다.

1-2. Optional

  • Optional은 null이 아닌 T 타입 참조를 하나 담거나, 아무것도 담지 않을 수 있다.
    그래서 Optional은 원소를 최대 1개 가질 수 있는 불변 컬렉션이다.

  • 자바 8부터는 위에서 얘기한 메서드가 값을 반환할 수 없는 상황인경우 Optional을 반환하면 된다. 왜?

    • Optional을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉽다.
    • null을 반환하는 메서드보다 오류 가능성이 적다.

2. 주요 메서드

2-1. of(), ofNullable(), empty()

  • Optioanl을 생성해주는 정적 팩터리 메서드들이다.
Optional.empty(); // 비어있는 Optioanl 반환
Optional.of(T); // 파라미터로 전달된 값을 Optional로 감싸서 반환, null 불가
Optional.ofNullbale(T); // 파라미터로 전달된 값을 Optional로 감싸서 반환, null 허용
  • of() 메서드에서 null값을 전달하면 NPE를 던진다.

  • null값도 허용하는 Optional을 만드려면 ofNullable()를 사용하자.

  • 추가로 Optional을 반환하는 메서드에서는 절대 null을 반환하지말자
    Optional을 도입한 취지를 완전히 무시하는 행위이다.

2-2. orElse() , orElseGet(), orElseThrow()

Optional<T>.orElse("기본 값"); // Optional이 비어있는 경우 기본 값을 설정할 수 있다.
Optional<T>.orElseGet("기본 값"); // Optional이 비어있는 경우 기본 값을 설정할 수 있다.
Optional<T>.orElseThrow(Exception::new); // Optional이 비어있는 경우 Exceptoin을 던진다.
  • orElse() 사용 시 Optional에 값이 있든 없든 무조건 실행된다.
    따라서 orElse(new ...) 처럼 새로운 객체를 생성하는 경우에는 orElseGet()을 사용하자.

    • orElseGet()은 Supplier를 사용해 Optional에 값이 비어있을때만 새로 생성한다.

2-3. isPresent(), ifPresent()

Optional.ifPresent(...); // Optional이 비어 있지 않으면 해당 값으로 로직 수행
Optional.isPresent(...); // Optional이 비어 있지 않으면 true 반환
@Transactional
public void signUp(SignUpDto signUpDto) throws UnsupportedEncodingException, MessagingException {
    if(userRepository.findByEmail(signUpDto.getEmail()).isPresent()){
        throw new CUserEmailOverlapException();
    } else if(userRepository.findByLoginId(signUpDto.getLoginId()).isPresent()){
        throw new CUserLoginIdOverlapException();
    }

    User user = signUpDto.toEntity(passwordEncoder);
    userRepository.save(user);

    sendEmail(user);
}
  • isPresent()는 예외처리할때 유용하게 썼다.
    예로 회원가입 하는경우 클라이언트가 전달한 정보로 DB에서 조회했을 때 이미 회원이 존재한다면? isPresent()를 이용해 예외를 던질 수 있다.

  • ifPresent()는 null값을 전달할 경우 NPE를 던질 수 있다고 명시되어 있으니 주의하자.

2-4. OptionalInt, OptionalLong, OptioanlDouble

  • 박싱된 기본타입(Integer, Long, Double 등)을 담는 Optional은 값을 두번이나 감싸기 때문에 무겁다.

  • 그래서 이를 위한 Optional 클래스를 제공한다.

    • OptionalInt, OptionalLong, OptionalDouble
  • 그러므로 박싱된 기본타입을 담는 Optioanl을 사용하는일은 없도록 하자.


3. 반환타입을 Optional로 사용

3-1. Optional을 반환하는 메서드 예시

  • 컬렉션에서 최대값을 구하는 메서드 - Optional 사용 X
public static <E extends Comparable<E>> E max(Collection<E> c) {
    if (c.isEmpty())
        throw new IllegalArgumentException("Empty collection");

    E result = null;
    for (E e : c)
        if (result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);

    return result;
}
  • 컬렉션에서 최대값을 구하는 메서드 - Optional 사용
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
    if (c.isEmpty())
        return Optional.empty();

    E result = null;
    for (E e : c)
        if (result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);

    return Optional.of(result);
}
  • Optional을 반환할때는 제공해주는 정적 팩터리 메서드를 사용하면 간단하다.

3-2 Optional로 반환하면 이런점이 좋다.

  • Optional은 검사 예외와 취지가 비슷하다. 즉 메서드의 반환값이 없을 수도 있다는걸 API 사용자에게 명확히 의미를 전달할 수 있다. Optional을 반환한다면 이를 호출하는 클라이언트에서는 반드시 이에 대처하는 코드를 추가해야 한다.

    • 위에서 null을 반환하는경우 null 처리를 위한 코드가 필요하므로 Optional이 등장했다고 이야기 했는데, Optioanl도 이를 처리하는 코드가 필요하니까 똑같은거 아니냐 할 수 있지만
      null에 비해 Optional은 명확한 의미전달 + 편리한 후처리 메서드 제공 + NPE 방지 등의 장점이 있다고 생각한다.

3-3. 이런 경우엔 Optional로 반환하면 좋다.

  1. 위에서도 말했듯이 결과가 없을 수 있는 경우
  2. 클라이언트가 이 상황을 특별하게 처리해야 하는 경우

3-4. 이런 경우엔 Optional로 반환하지 말자

  • 반환값으로 Optional을 사용한다고 무조건적으로 좋지 않다.
    • Optional도 새로 할당하고 초기화해야하는 객체이고, Optional에서 값을 꺼내기 위해선 메서드를 호출해야 하니 이것 또한 비용이다. 성능이 중요한 상황에서는 Optional이 맞지 않을 수 있다.

3-4-1. Collection, Stream, Array, Optional 같은 컨테이너 타입은 Optional로 감싸면 안된다.

  • Optioanl<List>보다는 그냥 빈 List를 반환하는게 더 좋다.
    빈 List를 반환하면 이를 호출하는 클라이언트에서는 Optional 처리 코드가 필요 없다.

3-4-2. Map의 값으로 Optional을 사용하면 절대 안된다.

  • Map 안에 키가 없다는 사실을 나타내는 방법이 두가지가 된다.

    1. 키 자체가 없는 경우
    2. 키는 있지만 Optional인 경우
  • 이로 인해 복잡성만 증가하고 오류 가능성을 키울 뿐이다.

  • 좀더 일반화 한다면 Optional을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는게 적정한 상황은 거의 없다.

3-4-3. 인스턴스의 필드에 Optional 사용

  • 이런 상황 대부분은 필수 필드를 갖는 클래스와, 이를 확장해 선택적 필드를 추가한 하위 클래스를 따로 만들어야함을 암시하는 좋지않은 신호이다.

  • 가끔 필드에 Optional을 사용하기 좋은 예외상황도 있다.

    • 인스턴스의 필드 중 필수가 아니고, primitive 타입이라 값이 없음을 나타낸 방법이 마땅하지 않은 경우이다.

3-5. 트레이드 오프?

  • Optioanl또한 새로 할당하고 초기화해야 하는 객체이고, Optional에서 값을 꺼내려면 메서드를 호출해야 한다. 이로 인해 성능이 중요한 상황에서는 알맞지 않을 수 있다. 라고 한다.

  • 하지만 이펙티브 자바 Item6 에서도 다룬 내용으로 “요즘 JVM에서 작은 객체를 생성하고 회수하는 일은 크게 부담되지 않는다. “ 라는 말이 있다.

  • 즉 Optional이 가져다주는 이점을 따져보면, 성능때문에 Optional 사용을 지양하자 라는 이야기는 알맞지 않다고 생각한다.

  • Stream과 마찬가지로 성능이 정말 중요한 서비스라면, 또는 Optional을 반환하는 메서드가 성능에 중요한 영향을 미치는 메서드라면 꼼꼼히 성능테스트를 해보고 Optinal을 제거함으로써 성능이 개선될 수 있다면 그 때 가서 고민해보는게 낫지 않나 라는 주관적인 의견이다.


#정리

💡 값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환값이 없을 가능성을 염두에 둬야 하는 메서드라면 Optional을 반환하는걸 고려하자.

Optional 반환에는 성능저하가 뒤따르니 남용하지말자. 하지만 Optioanl을 반환함으로써 얻는 이점이 많으니 사용하는걸 지향하고자한다.

Optional을 반환값 이외의 용도로 사용하는 경우는 거의 없다.


참고

좋은 웹페이지 즐겨찾기