자바 언어·플랫폼 용어 사전
함수형orElseGet · orElseThrow

Optional

'값이 없을 수 있음' 을 타입에 드러내는 상자. 반환 타입 전용이다.

null 을 돌려주면 받는 쪽은 그것을 알 수 없다. 문서를 읽거나 구현을 열어 봐야 하고, 대개는 확인하지 않아 NullPointerException 으로 나타난다. Optional 은 그 가능성을 타입 서명에 드러낸다.

Optional<Member> findByEmail(String email);   // 없을 수 있다고 서명이 말한다

컴파일러가 강제하는 것은 아니지만, 꺼내려면 한 단계를 거쳐야 하므로 없는 경우를 생각하게 만든다. 그것이 값어치의 대부분이다.

반환 타입 전용이다

  • 필드에 쓰지 않는다Serializable 이 아니라 직렬화가 막히고, 상자가 null 일 수도 있어 확인해야 할 것이 하나 늘어난다
  • 파라미터에 쓰지 않는다 — 호출자가 Optional.of(x) 로 감싸야 한다. 메서드를 둘로 나누거나 오버로드하는 편이 낫다
  • 컬렉션 원소로 쓰지 않는다 — "빈 결과" 는 Optional 이 아니라 빈 리스트로 표현한다. 빈 리스트는 그냥 순회하면 아무 일도 일어나지 않아 호출자가 분기할 필요가 없다

orElse 와 orElseGet 은 다르다

member.orElse(createGuest());       // createGuest() 가 항상 실행된다
member.orElseGet(() -> createGuest());  // 비었을 때만 실행된다

orElse인자를 평가한 뒤 메서드를 호출한다. 자바가 인자를 먼저 계산하기 때문이지 Optional 이 특별한 게 아니다. 값이 있어서 쓰이지 않을 뿐 계산은 이미 끝나 있다.

기본값이 상수라면 차이가 없다. 하지만 DB 를 읽거나 객체를 만드는 호출이라면 값이 있는데도 매번 그 일을 한다. 로그를 찍는 등 부수 효과가 있으면 아예 잘못 동작한다. 비싼 기본값에는 orElseGet 을 쓴다.

isPresent + get 은 쓰지 않는다

if (opt.isPresent()) { use(opt.get().name()); }        // null 검사와 다를 게 없다
opt.map(Member::name).ifPresent(this::use);            // 상자 안에서 처리한다

앞의 형태는 null 검사를 이름만 바꿔 쓴 것이라 얻는 것이 없다. map·filter·flatMap·ifPresent상자를 열지 않고 안에서 다루는 것이 원래 쓰임이다.

값이 없으면 예외를 던져야 한다면 orElseThrow 를 쓴다. 인자 없이 부르면 NoSuchElementException 이라 원인이 안 보이므로, 도메인 예외를 넘겨 주는 편이 낫다.

비용도 있다

객체를 하나 더 만든다. 대부분의 코드에서는 무의미한 비용이지만, 아주 짧은 루프 안에서 대량으로 만들면 GC 압박으로 나타난다. StreamOptionalInt·OptionalLong 같은 원시 타입 전용을 따로 두는 것도 박싱을 피하려는 것이다.

면접 함정

  • "Optional 을 쓰면 NPE 가 사라진다"get() 을 그냥 부르거나 상자 자체가 null 이면 똑같이 터진다.
  • "orElseorElseGet 은 취향"orElse 의 인자는 항상 평가된다. 부수 효과가 있으면 버그다.
  • "필드를 Optional 로 두면 안전하다" → 직렬화가 막히고 확인할 대상이 늘어난다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 함수형 프로그래밍·Stream