요즘 Spring Reactive로 작성된 코드를 Spring MVC로 변경하면서 Optional을 자주 활용하고 있다.
예를 들면 switchIfEmpty()
를 orElseGet()
으로 대체한다.
문득 orElse()
도 있는데, orElseGet()
대신 사용해도 괜찮지 않나? 라는 생각이 들었다.
하지만 두 메소드가 따로 구현된 이유가 있을 것이고, 둘의 차이를 잘 모르는데 아무거나 쓸 수는 없었다.
그래서 이번 기회에 제대로 이해하고, 어떤 상황에 어떤 것을 써야 하는지 알아야겠다 싶어 정리해본다.
orElse()
메소드 시그니처를 보면, 매개변수로 T
를 받고 리턴값으로 T
를 반환한다.
메소드 구현을 보면, 값이 null이 아니면 값을 반환하고 null이면 매개변수를 반환한다.
1
2
3
public T orElse(T other) {
return this.value != null ? this.value : other;
}
orElseGet()
메소드 시그니처를 보면, 매개변수로 Supplier를 받고 리턴값으로 T
를 반환한다.
(참고로 Supplier는 매개변수를 받지 않고 리턴값을 반환하는 함수형 인터페이스이다.)
메소드 구현을 보면, 값이 null이 아니면 값을 반환하고 null이면 Supplier의 get()
을 호출한다.
1
2
3
public T orElseGet(Supplier<? extends T> supplier) {
return this.value != null ? this.value : supplier.get();
}
학습 테스트
소스코드를 까보기만 했을 때는 값이 null일 때 처리하는 로직이 다른 것 외에는 무슨 차이가 있는지 파악하기 어려웠다.
좀 더 깊이 있게 이해하기 위해 학습 테스트를 진행했다.
코드 작성
다음과 같이 애플리케이션을 만들었다.
요새 어디든지 떠나고 싶어서, 여행지를 정해주는 매우 간단한 애플리케이션을 구현해봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Application.java
public class Application {
public static void main(String[] args) {
Trip trip = new Trip();
// 로직
}
}
// Trip.java
public class Trip {
public String choose() {
System.out.println("여행지 선택 중");
Destination destination = new Destination();
String city = destination.choose();
System.out.println("여행지 선택 완료");
return city;
}
}
// Destination.java
public class Destination {
public String choose() {
return "Victoria, Canada";
}
}
값이 null이 아닐 때
orElse()
Optional의 값이 null이 아닌데, orElse()
의 trip.choose()가 실행됐다.
결과는 기대대로 ‘San Francisco, USA’로 나왔다.
1
2
3
4
5
6
7
8
9
10
11
// Application.java
public class Application {
public static void main(String[] args) {
Trip trip = new Trip();
String city = Optional.of("San Francisco, USA").orElse(trip.choose());
System.out.println(city);
}
}
1
2
3
4
> Task :Application.main()
여행지 선택 중
여행지 선택 완료
San Francisco, USA
orElseGet()
Optional의 값이 null이 아니어서, orElseGet()
의 trip.choose()는 실행되지 않았다.
결과는 예상대로 ‘San Francisco, USA’로 나왔다.
1
2
3
4
5
6
7
8
9
10
11
// Application.java
public class Application {
public static void main(String[] args) {
Trip trip = new Trip();
Object city = Optional.of("San Francisco, USA").orElseGet(trip::choose);
System.out.println(city.toString());
}
}
1
2
> Task :Application.main()
San Francisco, USA
값이 null일 때
orElse()
Optional의 값이 null이어서, orElse()
의 trip.choose()가 실행됐다.
결과는 기대대로 ‘Victoria, Canada’가 나왔다.
1
2
3
4
5
6
7
8
9
10
public class Application {
public static void main(String[] args) {
Trip trip = new Trip();
String city = Optional.empty().orElse(trip.choose());
System.out.println(city);
}
}
1
2
3
4
> Task :Application.main()
여행지 선택 중
여행지 선택 완료
Victoria, Canada
orElseGet()
Optional의 값이 null이어서, orElseGet()
의 trip.choose()가 실행됐다.
결과는 예상대로 ‘Victoria, Canada’가 나왔다.
1
2
3
4
5
6
7
8
9
10
public class Application {
public static void main(String[] args) {
Trip trip = new Trip();
Object city = Optional.empty().orElseGet(trip::choose);
System.out.println(city.toString());
}
}
1
2
3
4
> Task :Application.main()
여행지 선택 중
여행지 선택 완료
Victoria, Canada
결론
orElse()
는 Optional의 값이 무엇이든 항상 로직이 실행된다.
orElseGet()
은 Optional의 값이 null일 때만 로직이 실행된다.
만약 요번 예제처럼 orElse()
/orElseGet()
에 어떤 객체를 생성하는 코드가 들어갈 때 orElse()
를 쓴다면, Optional의 값이 null이 아닌 경우 불필요한 객체가 계속 생성되는 문제가 발생한다.
이는 성능에 영향을 주고, 힙 메모리 공간을 불필요하게 차지하게 만든다.
그러므로 Optional의 값이 null일 때만 호출되는 orElseGet()
을 쓰는 게 좋다.
그동안 두 메소드의 차이를 정확하게 인지하지 못하고 사용해왔다.
요번 기회에 확실하게 알게 됐고, 앞으로는 orElseGet()
을 지향하며 상황에 맞는 메소드를 써야겠다.
References
- Optional 내부 코드 & 학습 테스트
- Java Optional – orElse() vs orElseGet()