Optional에서 orElse와 orElseGet

허진혁·2023년 9월 3일
0

기본기를 다지자

목록 보기
8/10

글의 목적

Java 8 이상에서 제공되는 Optional 클래스는 값이 존재하지 않을 수 있는 상황에서 더 명확하고 안전한 코드를 작성하기 위한 도구로 널(Null)을 다루는 데 유용해요.

Optional 클래스의 orElse() 메서드와 orElseGet() 메서드는 두 가지 다른 방식으로 기본값을 제공하는 데 사용돼요. 이 블로그에서는 두 메서드 간의 차이를 살펴보고 언제 어떤 메서드를 사용해야 하는지 알아보려해요.

orElse() 메서드

orElse() 메서드는 Optional 객체가 비어 있을 때 기본값을 제공해요. 다음은 orElse() 메서드의 사용 예시를 보면,

Optional<String> name = Optional.ofNullable(null);
String result = name.orElse("Unknown");

namenull인 경우 result에는 "Unknown"이라는 기본값을 할당해요.

orElse() 작동 방식

  1. Optional 객체가 비어있으면, 메서드에 전달된 기본값이 반환됩니다.
  2. Optional 객체가 값(NonNull)을 갖고 있으면, 그 값을 반환합니다. 따라서 기본값은 무시됩니다.
  3. 기본값을 미리 계산하고 전달하므로, orElse() 메서드가 호출되면 기본값을 항상 평가합니다.

orElseGet() 메서드

orElseGet() 메서드는 Optional 객체가 비어있을 경우에만 사용되며, 기본값을 제공할 때 Supplier 인터페이스를 통해 지연 생성을 지원해요.따라서 Optional이 비어있을 때만 기본값을 생성하여 불필요한 리소스 낭비를 줄일 수 있어요.

Optional<String> name = Optional.ofNullable(null);
String result = name.orElseGet(() -> expensiveOperation());

namenull인 경우, expensiveOperation() 함수를 호출하여 기본값을 계산해요.

orElseGet() 작동 방식

  1. Optional 객체가 비어있으면, Supplier 함수를 호출하여 기본값을 계산합니다.
  2. Optional 객체가 값(NonNull)을 갖고 있으면, 그 값을 반환합니다. 따라서 Supplier 함수는 호출되지 않습니다.
  3. 기본값을 필요할 때만 계산하므로, 불필요한 계산을 피할 수 있습니다.

언제 어떤 메서드를 사용해야 할까?

  • orElse()를 사용하면 기본값을 항상 계산해요. 기본값이 비용이 많이 드는 연산이거나 민감한 연산일 경우 orElseGet()을 사용하면 불필요한 계산을 피할 수 있어요.
  • orElseGet()은 지연 계산을 통해 성능을 최적화할 수 있으며, 값이 존재할 때의 계산만 필요한 경우에 적합해요.
  • 두 메서드 중 어떤 것을 선택하더라도 Null 체크와 예외 처리를 줄이고 가독성을 높이기 위해 Optional을 사용하는 것은 좋은 방식이에요.

예시 1

public static void main(String[] args) {
        /*System.out.println(1 / 2);
        System.out.println(Math.floor(1 / 2));
        }*/
        Optional<String> optional1 = Optional.ofNullable("nonNull");
        System.out.println(optional1.orElse(test()));
        System.out.println(optional1.orElseGet(() -> test()));

        System.out.println("----------");

        Optional<String> optional2 = Optional.ofNullable(null);
        System.out.println(optional2.orElse(test()));
        System.out.println(optional2.orElseGet(() -> test()));
        System.out.println();
    }
    private static String test(){
        System.out.println("call test");
        return "return test";
    }

위에서 main 메서드를 시키면 다음과 같이 출력되요.

// 객체에 값이 있는 경우
call test
nonNull
nonNull
----------
// 객체에 값이 없는 경우
call test
return test
call test
return test

Optional 객체 안에 값이 있을 경우에 orElse() 메서드는 기본값을 호출 한 후(”call test”) 값이 없으면 return을 하지 않는 것이고, orElseGet() 메서드는 값이 있으니 지연 생성 방식을 통해 기본값을 생성하지도 않는 것이에요.

정리하면

orElse()는 null인지 아닌지 상관없이 호출하고,
orElseGet()은 null 일때만 호출해요.

예시 2

유저이름을 통해 유저 데이터를 가져오는데, 없는 경우 새로 만든다는 가정을 해보았을 때, 다음과 같이 간단한 코드를 만들었어요. (name은 unique로 설정되어 있다고 가정할게요.)

public User findByName(String name) {
	return userRepository.findByUsername(name).orElse(createUser(name));
}

private User createUser(String name) {
	User user = User.bulider()
					.name(name)
					.build();

  return userReository.save(user);
}

위와 같은 코드는 버그를 발생시킬 것이에요. 위에서 설명한 호출 순서를 상기시켜 정리해보면 다음과 같아요.

  1. findByUsername 메서드가 호출되면, userRepository.findByName(name) 메서드를 통해 name에 해당하는 사용자를 찾으려고 합니다.
  2. userRepository.findByName(name)Optional<User>를 반환합니다. 이때, 두 가지 시나리오가 가능합니다:
    • name에 해당하는 사용자가 존재하면 Optional에는 사용자 객체가 포함됩니다.
    • name에 해당하는 사용자가 존재하지 않으면 Optional은 비어있는 상태가 됩니다.
  3. orElse 메서드가 호출됩니다. 이때, Optional이 비어있는 경우 createUserWithName(name) 메서드가 호출됩니다.
  4. createUserWithName(name) 메서드에서 새로운 사용자 객체가 생성되고, 이 사용자 객체는 userRepository를 통해 저장됩니다.
  5. 하지만, 문제는 createUserWithName(name) 메서드가 항상 호출된다는 것입니다. 즉, 사용자가 이미 존재하는 경우에도 createUserWithName(name)이 호출되어 중복 사용자가 생성됩니다.

정리

엄청난 성능을 요하는 어플리케이션에서 사용된다면 두 메서드의 성능 차이를 고려해야 하겠지만, 이런 경우 성능보다는 가독성에 더 중점적으로 생각할 필요가 있어요. 무엇보다도 orElseorElseGet 의 호출 시점이 다르다는 점은 꼭 확인하고 사용해야 해야 겠어요 !!

profile
Don't ever say it's over if I'm breathing

0개의 댓글