언어/JAVA

[JAVA] Optional의 orElse()와 orElseGet()의 차이점

SeongOnion 2022. 8. 26. 16:01
728x90

자바는 null값을 적절하게 처리하도록 하기 위해 Optional 이라는 Wrapper 클래스를 제공한다.

 

Optional 클래스의 기능 중 orElse()orElseGet() 은 모두 Optional로 가져온 값이 null일 경우, 해당 값을 무엇으로 대체할지 결정해주는 메서드이다.

 

지금까지 두 메서드의 차이점을 어렴풋이만 알고 정확히는 모른 채 Supplier로 써주기 귀찮다는 이유로 orElse()를 주로 사용해왔는데, 사실은 굉장히 위험하고 생각없는 짓이었다.

 

 

orElse()orElseGet()은 모두 Optional의 값이 null인 경우 사용자가 지정한 다른 값을 넘겨주는 용도이다.

 

핵심적인 차이점은 파라미터로 받는 값이 TSupplier라는 것이다.

 

이게 뭐? 라고 생각할 수 있지만(내가 그랬음), 만약 orElse()의 파라미터 내에서 메서드를 호출하게 된다면 해당 메서드는 Optional 값이 null이든 아니든 무조건 호출된다.

 

더 나은 이해를 위해 직접 코드를 짜보며 테스트 해보자.

public void optionalOrElseTest() {
        Member member = memberRepository.findByUsername("aaa")
                .orElse(memberRepository.save(new Member("bbb")));
    }

아래는 Member 객체를 찾고, 만약에 값이 없다면 새로운 값을 생성해서 넣어주는 로직이다.

 

orElse()orElseGet() 모두를 사용해서 해당 코드를 한 번 테스트 해보자

 

orElse() 사용

@SpringBootTest
@Transactional
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository.save(new Member("aaa"));
    }

    @Test
    public void optionalOrElseTest() {

        Member member = memberRepository.findByUsername("aaa")
                .orElse(memberRepository.save(new Member("bbb")));

        List<Member> memberList = memberRepository.findAll();

        for (Member eachMember : memberList) {
            System.out.println("eachMember.getUsername() = " + eachMember.getUsername());
        }
    }

}

먼저, @BeforeEach 로 미리 "aaa"라는 username을 가진 Member 데이터를 DB에 저장해주었다.

 

테스트에선 "aaa"라는 username으로 Member 객체를 조회한 후, 만약 값이 없으면 "bbb"라는 username을 가진 Member 객체를 저장할 것이다.

 

이후, DB에 존재하는 모든 Member 데이터를 조회해 각 Member의 username을 프린트할 것이다.

 

내 예상대로라면(내가 잘못 알고 있던 바로는), "aaa"라는 username을 가진 Member는 이미 DB에 존재하므로, memberRepository.save(new Member("bbb")) 메서드는 호출되지 않게 될 것이다.

 

그러므로, 결과적으로 출력되는 username은 "aaa" 하나일 것이다.

 

하지만 결과는 다음과 같았다.

 

 

위에서 언급했듯, 이러한 결과가 발생하는 이유는 orElse()는 파라미터로 T, 즉 객체를 받기 때문이다.

 

다시말해, 애초에 orElse() 로직이 돌아가기 전부터 파라미터인 memberRepository.save(new Member("bbb")) 는 저장된 객체로 세팅되어 파라미터로 들어가게 된다.

 

즉, 위의 테스트 코드는 사실 아래와 같은 로직의 코드가 되는 것이다.

@Test
public void optionalOrElseTest() {
	Member newMember = memberRepository.save(new Member("bbb"));
    
    Member member = memberRepository.findByUsername("aaa")
            .orElse(newMember);

    List<Member> memberList = memberRepository.findAll();

    for (Member eachMember : memberList) {
        System.out.println("eachMember.getUsername() = " + eachMember.getUsername());
    }
}

 

orElseGet() 사용

반면에, orElseGet() 은 파라미터로 Supplier를 받기 때문에 우리가 원하는대로 값이 null일 때 비로소 memberRepository.save(new Member("bbb")) 를 호출하게 된다.

@Test
public void optionalOrElseGetTest() {

    Member member = memberRepository.findByUsername("aaa")
            .orElseGet(() -> memberRepository.save(new Member("bbb")));

    List<Member> memberList = memberRepository.findAll();

    for (Member eachMember : memberList) {
        System.out.println("eachMember.getUsername() = " + eachMember.getUsername());
    }
}

내가 원했던대로 로직이 수행된 모습이다.

 

결론

위에 예시로 든 로직에서 orElse()orElseGet() 의 차이를 정확히 파악하지 못하고 orElse() 를 사용하게 되면 치명적인 장애가 날 수 있다.

 

orElse() 는 상수나 정책상 디폴트로 정해진 값을 넣을 때 사용하도록 하고, 앵간하면 파라미터 내에서 메서드를 호출하기보단 우선 바깥에서 객체로 만들어놓고 해당 객체를 직접 넘기는 방식으로 사용할 수 있도록 하자.