본문 바로가기
Java

Optional orElse, orElseGet 차이

by 에어컨조아 2022. 6. 15.

JAVA 8에서는 Optional을 사용할 수 있습니다.

Optional에서는 다양한 기능들을 제공하는데요! 그 중 orElse, orElseGet에 대해서 정확히 알고 사용해야 할 부분이 있어 작성하게 되었습니다.

자세한 기능설명을 하기위해 클린코드에서 배운 학습 테스트를 해보겠습니다.

class OptionalTest {

    @Test
    void test1() {
        String print = "Optional test";

        String result1 = Optional.ofNullable(print).orElse(nullPrint1());
        String result2 = Optional.ofNullable(print).orElseGet(this::nullPrint2);

        assertThat(result1).isEqualTo(print);
        assertThat(result2).isEqualTo(print);
    }

    private String nullPrint1() {
        System.out.println("test1 출력되나?");
        return "null 입니다";
    }

    private String nullPrint2() {
        System.out.println("test2 출력되나?");
        return "null 입니다";
    }
}

테스트를 돌리면 성공하는 것을 확인하실 수 있습니다.

그런데 눈치 채셨나요? Console log를 확인해보면 orElse에서 ‘test1 출력되나?’ 가 출력되는 것을 확인하실 수 있습니다.

그렇다면 왜 orElse에서는 println이 출력되고, orElseGet은 출력이 되지 않은 걸까요?

🧐orElse와 orElseGet 동작원리

우선 내부 코드를 확인해 보겠습니다.

Untitled (5)

Untitled (6)

둘의 차이는 보다시피 파라미터가 조금 다르다는 것 말고는 큰 차이가 없습니다.

그럼 문제의 원인은 파라미터라는 것인데요! 왜 그럴까요?

JAVA 7까지는 함수를 매개변수로 넘기는 것이 불가능 했습니다. (자바는 일급객체가 아닙니다.)

무조건 원시타입이나 객체들을 넘길 수 있었습니다. 그런데 JAVA 8에 Functional Interface 개념이 도입되고 나서 자바에서도 함수를 매개변수로 전달할 수 있는 방법이 생겼습니다.

저기서 보이는 Supplier 인터페이스도 JAVA 8에 추가된 Functional Interface 입니다.

자! 그럼 다시 문제를 상기시켜 볼까요?

다시 한번 문제가 되는 코드를 확인해 보겠습니다.

String result1 = Optional.ofNullable(print).orElse(nullPrint1());
String result2 = Optional.ofNullable(print).orElseGet(this::nullPrint2);

위 코드에서는 똑같이 함수를 매개변수로 전달했는데 orElse에서는 내부 메서드가 실행되는데, orElseGet에서는 왜 호출되지 않을까요?

orElse의 타입은 제네릭 이지만 orElseGet은 함수를 그대로 받을 수 있는 Functional Interface이기 때문입니다.

제네릭은 해당 클래스에서 사용할 타입을 외부에서 주입받을 수 있도록 하는 개념입니다. 타입관점으로 말하자면 컴파일 타임에 타입체킹이 끝나고 런타임 시점에는 최소한의 정보로 동작합니다.

즉 컴파일 타임에서 타입체킹이 완료된 코드임으로 JVM 환경에서 정상동작 할 것이며, 제네릭으로 받은 orElse는 실제 메서드를 호출하여 값을 출력하게 된 것입니다.

말로만 말씀드리면 헷갈리니 코드로 작성해보겠습니다.

String nullPrint = nullPrint1();
String result1 = Optional.ofNullable(print).orElse(nullPrint);

아마 내부적으로 저런식으로 메서드를 실제 호출하고 그 값을 orElse의 매개변수로 넣어줄 것입니다.

이에 반해 orElseGet은 메서드를 그대로 수신할 수 있는 Functional Interface 임으로 메서드 그대로 전달하게 됩니다.

Supplier<String> nullPrint = () -> nullPrint2(); // 익명 메서드를 사용
String result2 = Optional.ofNullable(print).orElseGet(nullPrint); // 익명 메서드를 전달

따라서 orElse는 자바가 일급객체가 아닌 이유를 보여준 적절한 예시이며, orElseGet메서드는 그나마 흉내라도 낼 수 있을 정도로 Functional Interface를 제공해 주는 것을 알 수 있습니다.

주의사항

orElse, orElseGet의 특성상 null 값일 경우 이에 따른 복구 절차를 추가할 것이라 생각합니다.

이때 정확하게 인지하지 않은 상태에서 사용한다면! 또 내부에 DB에 insert, update 문이 포함되어있다면? 생각만해도 아찔합니다😨😱

댓글