본문 바로가기
개발 관련/java

orElse 와 orElseGet 무슨 차이가 있을까?

by lazysnack 2022. 7. 14.

Java 8을 쓰면서 null 에 대한 처리를 위해서 Optional 을 많이 쓸 텐데요.

저도 꽤 즐겨 쓰는 편입니다. (보통 null 을 반환하는 로직을 짜고 싶지 않겠지만, 그게 항상 마음대로 되지는 않잖아요..)

또한 적절하게 쓰면 코드 길이를 줄여 한 줄로 가능하다는 장점도 있고요.

Optional 에는 orElse() 라는 메소드와 orElseGet() 이라는 메소드가 있는데요.

둘 다 Optional 을 통해 가져온 값이 null 일 때는 해당 값을 반환하라는 메소드 입니다.

최근에 관련 메소드를 쓰다가 전혀 생각지도 못한 곳에서 에러가 나서 재배포를 한 기억이 있는데요.

(핫픽스를 통해 재배포를 했습니다. 하하..)

그러면 좀 더 잘 알고 사용하여, 다음엔 같은 에러를 발생시키지 않도록 정리를 해볼까 합니다.

1. orElse 와 orElseGet

일단 두 메소드를 확인하기 위해 Optional 클래스를 보면 이런 내용이 있습니다.

/**
 * Return the value if present, otherwise return {@code other}.
 *
 * @param other the value to be returned if there is no value present, may
 * be null
 * @return the value, if present, otherwise {@code other}
 */
public T orElse(T other) {
  return value != null ? value : other;
}

/**
  * Return the value if present, otherwise invoke {@code other} and return
  * the result of that invocation.
  *
  * @param other a {@code Supplier} whose result is returned if no value
  * is present
  * @return the value if present otherwise the result of {@code other.get()}
  * @throws NullPointerException if value is not present and {@code other} is
  * null
  */
public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get();
}

간단하게 보면 두 메소드 다 value 가 null 이면 other 을 return 하는 형태입니다.

차이가 있다면 orElse() 의 경우에는 T 타입의 other 을 그대로 반환해주는 역할을 하고, orElseGet() 의 경우에는 Supplier 의 인터페이스를 통해 그 인터페이스의 결과(?) 를 반환한다고 볼 수 있습니다.

이것만 보면 결과적으로는 두 메소드 다 T라는 타입의 값을 반환하기 때문에, 같아 보입니다만..

같으면 메소드를 두개 만들 필요가 없겠죠. 그리고, 저도 에러와 만나지 않았을테고요 ㅎㅎ

관련되서 두 메소드의 차이점을 찾아보면 이런 얘기가 나옵니다.

  • orElse 메소드는 해당 값이 null 이든 아니든 관계없이 항상 불린다.
  • orElseGet 메소드는 해당 값이 null 일 때만 불린다.

그러면 해당 내용을 확인 하기 위해 간단한 테스트 코드를 작성해서 보겠습니다.

@Test
@DisplayName("notNull테스트")
void name() {
    String name = "snack";
    String elseName = Optional.ofNullable(name).orElse(anyName());
    System.out.println(elseName);

    String elseGetName = Optional.ofNullable(name).orElseGet(this::anyName);
    System.out.println(elseGetName);
}

private String anyName() {
    System.out.println("reach anyName");
    return "anyName";
}

위의 테스트 코드를 돌려보면 다음과 같은 결과가 나옵니다.

 reach anyName
 snack
 snack

그리고 name 에 null 을 넣어서 테스트를 진행해보면

@Test
@DisplayName("null테스트")
void name2() {
    String name = null;
    String elseName = Optional.ofNullable(name).orElse(anyName());
    System.out.println(elseName);

    String elseGetName = Optional.ofNullable(name).orElseGet(this::anyName);
    System.out.println(elseGetName);
}
reach anyName
anyName
reach anyName
anyName

이러한 결과가 나옵니다.

orElse 일 때는 null 여부와 관계없이 동작하고, orElseGet 은 null 일 때만 동작하였습니다.

그런데..?

위의 Optional 클래스의 orElse() 와 orElseGet() 을 잘 보면...

return value != null ? value : other; // orElse(T other)
return value != null ? value : other.get(); // orElseGet(Supplier<? extends T> other)

별 차이가 없습니다. 즉, 값이 null이든 아니든 orElse, orElseGet 둘 다 호출이 됩니다.


2. orElse 에 대해

둘 다 호출이 되는 것은 알겠습니다.

다시 두 메소드의 역할로 돌아가보죠.

두 메소드는 null 일 때 대체값을 반환하기 위한 메소드입니다.

그러면 orElse 에서 null 이 아닌 경우에도 reach anyName 을 출력한 이유는 무엇일까요?

좀 간단한 얘기일 수도 있겠는데요. 이건 orElse 파라미터 T other 에 들어간 other 가 메소드 이기 때문입니다.

public T orElse(anyName()) {
  return value != null ? value : anyName();
}

위에서 테스트한 메소드를 풀어보면 이런 식으로 동작을 할 것입니다. 여기서 T 는 anyName() 이 아니라 anyName 이라는 String 값이죠. 그렇기 때문에 orElse 입장에서는 anyName() 을 실행하여 그 값을 반환해줘야 하는 것이죠.

실제로 제가 이번에 겪은 문제도 이와 같은 문제였는데요.

public List<TabMenu> getTabMenu (UserInfo userInfo) {
    return List<TabMenu> tabMenus = Optional.ofNullable(getDBData(userInfo))
      .orElse(fallback());
}

private List<TabMenu> fallback() {
  // fallback yml 데이터를 읽어옴
}

대략 이런 식으로 유저의 Menu 를 가져오는 로직이 있었는데요.

fallback 에서 yml 데이터를 파싱하는 부분에서 에러가 발생하여, getTabMenu 메소드 자체가 동작하지 않아서 에러가 발생하는 문제였습니다.

해당 부분을 orElse 대신 orElseGet 으로 수정하는 것으로 문제를 해결했는데요.

저의 경우엔 큰 문제가 없었지만, orElse 부분에 insert 나 update 등의 DB 를 조정하는 행위가 있었으면, 자칫 큰 문제로 발생할 뻔 했습니다. (휴)


3. orElseGet 에 대해

그러면 반대로 생각을 할 수도 있을 것 같네요.

왜 orElseGet 은 null 일 때 호출을 안하는 것이지? 라고요.

실제로 저도 orElse 가 null 이 아니어도 호출한다는 생각보다 위의 생각이 먼저 들었었는데요.

public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get();
}

메소드를 다시 보면, other 를 바로 실행하는 것이 아닙니다.

method 를 전달하고, value 가 null 이면 other.get() 즉, 메소드를 실행하죠.

그렇기 때문에 실행이 안되는 것입니다.


4. 마치며

  • orElse 와 orElseGet 에 대해서 알아봤습니다. 내부를 보면 심플해보이고, 실제로 깔끔하기도 합니다만, else 행위에 대한 메소드를 작업할 때는 조심해야 할 필요가 있어보입니다. (특히 JPA 등을 사용하여 Entity가 변경이 되는 부분이 캡슐화되면 찾기가 어려울 수도 있으니까요 :) )

  • 처음에는 orElse 를 잘 사용하지 않았습니다. 주로 orElseGet(() -> "") 의 형태로 주로 사용했죠. (아마 처음 Optional 을 사용하기 위해 보통의 글을 읽고, 무조건적으로 orElseGet 을 써야지 했던 이유가 아닐까 싶네요.) 그런데, 이렇게 사용하다 보니 값에 대해서는 IDEA 에서 orElse 로 변경으 유도하더군요. 그러다보니 orElse 을 사용하고, 결과적으로 확실히 집고 넘어가지 않고, 혼용된 형태를 사용하다가 이런 상황이 발생한 것 같네요. 역시 명확히 알고 써야 합니다. (하하..)

  • 참고

'개발 관련 > java' 카테고리의 다른 글

캡슐화(encapsulation)  (0) 2022.07.14
Transactional 정리  (0) 2022.07.14
멀티쓰레드에서 Thread-Safe 방법  (0) 2022.07.14
SimpleJdbcInsert 문제  (0) 2022.07.14
DB Insert 시 자동생성된 id 를 알아내기  (0) 2022.07.14