orElse()와 orElseGet() 을 비교해보자. 비슷한것 같지만 다르다.
먼저 즉시평가와 지연평가에 대해 알아보자.
즉시 평가 vs 지연 평가
즉시 평가는 값을 바로 생성하거나 계산하는 것이고, 지연 평가는 값이 실제로 필요할 때까지 연산을 미뤘다가 필요시점에 연산하는 방법이다.
로그를 예로 들어 즉시 평가와 지연평가 예시를 보자.
log.info("로그를 쓰는 방법이 개같군요 당신의 이름은 = " + name); -> 이게 즋기평가이다.
log.info("로그를 쓰는 방법이 적절하군요 당신의 이름은 = {}", name); -> 이게 지연평가이다.
즉시 평가
자바는 기본적으로 연산을 즉시 평가한다. 즉, 10+20 이라는 연산을 처리할 순서가 되면 즉시 평가한다.
이게 왜 문제가 될까?
만약 우리가 미리 example 이란 변수에 10 + 30 을 저장했다고 가정하자. 하지만 우리가 만약 코드 내부에서 이 example 변수를 사용하지 않게 되면 어떻게 될까?
example 변수에 계산식은 실행 순서가 되면 계산이 실행되고, 코드 내부에서 사용하지 않으면 GC에 의해 정리된다.
즉, 사용하지도 않을 자원때문에 아까운 CPU 자원만 사용한 셈이 되는 것이다.
그렇다면 미래에 사용할 수도 있고 사용하지 않을 수도 있는 연산을 미리 수행되지 않도록 하는 방법이 없을까?
-> 이 문제를 해결하기 위해 연산을 정의하는 시점과 연산을 실행하는 시점을 분리하여 최대한 연산을 지연해서 평가해야한다.
지연 평가
연산을 정의하는 시점과 실행하는 시점을 람다(익명클래스)로 분리하여 지연평가 하도록 해보자.
import java.util.function.Supplier;
public class Logger {
private boolean isDebug = false;
public Logger(boolean isDebug) {
this.isDebug = isDebug;
}
public boolean isDebug() {
return isDebug;
}
public void setDebug(boolean debug) {
isDebug = debug;
}
//DEBUG로 설정한 경우만 출력 - 데이터를 받음
public void debug(Object message) {
if (isDebug) {
System.out.println("[DEBUG] " + message);
}
}
// 추가
// DEBUG로 설정한 경우만 출력 - 람다를 받아서 실행
public void debug(Supplier<?> supplier) {
if (isDebug) {
System.out.println("[DEBUG] " + supplier.get());
}
}
}
맨 밑에 debug 메서드를 보자. supplier 람다를 받는 메서드이다.
이 Logger 클래스는 만약 isDebug가 ture 일때만 로그가 찍히도록 하는 코드이다.
이 Supplier 는 get을 실행하는 시점에 해당 람다를 연산하고 그 결과를 반환한다.
쉽게 생각하면 익명 클래스를 정의 해놓고, 해당 get 메서드를 나중에 호출하는 것과 같다.
package optional.logger;
import java.util.function.Supplier;
public class LogMain3 {
public static void main(String[] args) {
Logger logger = new Logger(true);
logger.setDebug(true);
logger.debug(() -> value100() + value200());
System.out.println("=== 디버그 모드 끄기 ===");
logger.setDebug(false);
logger.debug(() -> value100() + value200());
}
static int value100() {
System.out.println("value100 호출");
return 100;
}
static int value200() {
System.out.println("value200 호출");
return 200;
}
}
이후 실행하는 코드를 한번 보면, 디버그 모드를 false로 한 부분을 보자. 디버그 모드가 false이기 때문에 debug 메서드를 통한 로그가 출력되면 안된다.
그리서 우리는 위에서 람다를 넘겨 정의 시점과 실행 시점을 분리하도록 했다.
코드 흐름은 다음과 같다.
1. logger.debug를 만나서 람다 생성 (람다가 생성만 되고 실행은 되지 않는다)
2. Logger 객체에서 isDebug 조건 확인
3 - 1. isDebug가 true면 supplier.get 을 실행
3 - 2. isDebug가 false면 조건문 실행 X
결론적으로 람다를 통해 연산 정의 시점 과 실행 시점을 분리할 수 있게 되었다. 즉, 연산이 필요할 때까지 지연평가 전략을 통해 해당 연산을 뒤로 미룰 수 있게 되었다.
orElse() vs orElseGet()
orElse()는 즉시평가 전략을 사용하고, orElseGet()은 지연평가를 사용한다. 그래서 둘의 파라미터도 다른 것이다.
orElse()는 Optional에 값이 들어 있어도, 파라미터 안에 연산이 반드시 실행 된다. 그리고 사용하지 않으면 버려진다.
당연히 자바 연산 순서상 파라미터에 대한 결과를 orElse()의 인자로 전달할 수 있기 때문이다.
orElseGet()은 Optional에 값이 있으면 파라미터 자체가 실행되지 않는다(구체저으로는 람다가 생성만 되고 실행되진 않는다).
값이 없다면 그제서야 람다연산을 실행하고, 해당 Optional의 내부 값으로 대체 된다.
차이점
orElse(other)는 빈값이면 other을 반환하는데 이때 other은 항상 즉시 계산된다. -> 사용하지 않더라도 값이 계산된다.
orElseGet(Supplier supplier)은 빈 값이면 supplier 람다를 통해 값을 생성하기 때문에 값이 있다면 람다를 실행하지 않는다.
사용 전략
값이 이미 존재하거나, 존재할 가능성이 높고, 해당 객체 생성 비용이 크지 않은 경우에는 orElse()를 사용해도 괜찮고, 상수에도 사용해도 괜찮다.
하지만 객체의 생성값이 큰 경우에는 사용하지 않을 가능성을 고려하여 orElseGet()를 사용하는 전략을 택하자.
'Java > Optional' 카테고리의 다른 글
| [Java] Optional의 Best Practice (0) | 2025.08.09 |
|---|---|
| [Java] Optional과 생성, 값 획득 및 처리 (0) | 2025.08.09 |
| [Java] Optional이 필요한 이유 (0) | 2025.08.09 |