[JAVA] Optional의 orElse()와 orElseGet()의 차이점
자바는 null
값을 적절하게 처리하도록 하기 위해 Optional
이라는 Wrapper 클래스를 제공한다.
Optional
클래스의 기능 중 orElse()
와 orElseGet()
은 모두 Optional
로 가져온 값이 null
일 경우, 해당 값을 무엇으로 대체할지 결정해주는 메서드이다.
지금까지 두 메서드의 차이점을 어렴풋이만 알고 정확히는 모른 채 Supplier
로 써주기 귀찮다는 이유로 orElse()
를 주로 사용해왔는데, 사실은 굉장히 위험하고 생각없는 짓이었다.
orElse()
와 orElseGet()
은 모두 Optional
의 값이 null
인 경우 사용자가 지정한 다른 값을 넘겨주는 용도이다.
핵심적인 차이점은 파라미터로 받는 값이 T
와 Supplier
라는 것이다.
이게 뭐? 라고 생각할 수 있지만(내가 그랬음), 만약 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()
는 상수나 정책상 디폴트로 정해진 값을 넣을 때 사용하도록 하고, 앵간하면 파라미터 내에서 메서드를 호출하기보단 우선 바깥에서 객체로 만들어놓고 해당 객체를 직접 넘기는 방식으로 사용할 수 있도록 하자.