[Java] Optional의 사용법과 주의사항

Kai·2024년 7월 24일
0

Java

목록 보기
20/22

Optional 초기화


1) of()와 empty()

	@Test
    void 초기화() {

        Optional<String> opt = Optional.of("Hello");
        Optional<String> opt3 = Optional.empty();

    }

Optional 객체를 초기화하는 가장 기본적인 방법은 2가지가 있다. 하나는 비어있는 Optional을 만드는 것(.empty())이고, 또 하나는 특정 값을 감싸서 Optional을 만들어주는 방법(.of())이다.

주의할 점은 Optional.of(null)과 같이 .of()null 값이 들어가면, NPE가 발생한다는 점이다. 대부분의 경우 Optional을 쓰는 이유는 어떠한 변수에 확정적으로 값이 할당되는 것이 보장되지 않고, null이 들어갈 수도 있어서 이 변수를 처리하는 로직에서 NPE가 발생하지 않도록 예방하는 것인데, null이 들어갔을 때 에러가 나도록 되어 있다면,,, 무슨 의미인가 싶다 ?!?

그래~서 이러한 경우도 처리할 수 있는 방법 있는데 바로 이어서 확인해보자.

2) ofNullable()

	@Test
    void 초기화() {
    
        Assertions.assertThatThrownBy(() -> Optional.of(null))
            .isInstanceOf(NullPointerException.class);

        Optional<String> opt = Optional.ofNullable(null);

    }

이어서 이야기하자면, null이 될 수도 있는 값을 Optional로 만들고 싶다면 Optional.ofNullable()을 사용하면 된다. 🤓



값 체크


Optional에 값이 있는지 여부에 따라서 이후 처리들이 달라지는 경우가 많을 텐데, 이번에는 Optional이 비어있는지 아닌지 체크하는 메서드들에 대해서 알아보자.

1) isPresent, isEmpty

    @Test
    void 값의_존재_여부_체크() throws Exception {

        assertThat(Optional.of("Hello").isPresent()).isTrue();
        assertThat(Optional.empty().isEmpty()).isTrue();

    }

메서드 명에서도 알 수 있듯이 .isPresent()는 Optional에 값이 있으면 true를 리턴하는 메서드이고, isEmpty()는 Optional이 비어 있으면 true를 리턴하는 메서드이다.

2) ifPresent

    @Test
    void 값의_존재_여부_체크() throws Exception {

        Optional.of("Hello").ifPresent(s -> {
            log.info("{} 있다", s);
            // 무언가 수행
        });

    }

값의 존재 여부를 체크하면서 값이 존재할 경우 특정 로직을 수행할 수 있는 ifPresent()도 있다.

    /**
     * If a value is present, performs the given action with the value,
     * otherwise does nothing.
     *
     * @param action the action to be performed, if a value is present
     * @throws NullPointerException if value is present and the given action is
     *         {@code null}
     */
    public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }

여기서 수행할 로직은 ifPresent()에 Consumer로 넘겨주면 된다.



값이 없을 때


Optional이 비어 있는 경우, 단순히 비어 있는 것을 확인하는 것만 아니라 다양한 처리를 추가할 수 있는데, 그 방법들을 알아보자.

1) orElse, orElseGet

    @Test
    void orElse() throws Exception {

        Optional<Object> emptyOptional = Optional.empty();

        assertThat(emptyOptional.orElse("abc")).isEqualTo("abc");
        assertThat(emptyOptional.orElseGet(() -> "abc")).isEqualTo("abc");

    }

값이 비어있을 때, 대체할 값을 리턴하고 싶다면, .orElse()를 사용하고, 특정 메서드를 통해서 대체할 값을 리턴하고 싶다면, .orElseGet()을 사용할 수 있다.

이 둘의 역할은 동일하고, 상황에 맞게 적합한 것을 사용하면 된다.

2) orElseThrow

    @Test
    void orElse() throws Exception {

        Optional<Object> emptyOptional = Optional.empty();
        
        assertThatThrownBy(() -> emptyOptional.orElseThrow(() -> new IllegalArgumentException("비어 있음")))
            .isInstanceOf(IllegalArgumentException.class);

    }

값이 비어 있을 때, 예외를 던지고 싶다면 .orElseThrow()를 사용하자.



실제 값 가져오기


1) get

    @Test
    void 값_가져오기() throws Exception {
        assertThat(Optional.of("abc").get()).isEqualTo("abc");

        assertThatThrownBy(() -> Optional.empty().get())
                .isInstanceOf(NoSuchElementException.class);
    }

Optional은 어쨋든 NPE를 예방하기 위한 객체이고, 실제적인 로직을 처리하기 위해서는 Optional에 담겨 있는 실제 값을 가져와야하는 경우가 많다.

이 때 사용할 수 있는 가장 기본적인 메서드가 .get()이다. 단, 비어있는 Optional에 .get()을 사용하면 NoSuchElementException예외가 발생한다.

2) filter

    @Test
    void 값_가져오기_filter() throws Exception {
        Optional<String> opt = Optional.of("abc");

        assertThat(opt.filter(s -> "abc".equals(s)).get()).isEqualTo("abc");
        assertThatThrownBy(() -> opt.filter(s -> "hello".equals(s)).get())
                .isInstanceOf(NoSuchElementException.class);
    }

특정 조건을 만족하는 값만을 걸러낼 수 있는데, 이 때 사용할 수 있는 메서드가 .filter()이다.

3) map

    @Test
    void 값_가져오기_map() throws Exception {

        Optional<String> opt = Optional.of("abc");

        assertThat(opt.map(String::length).get()).isEqualTo(3);
    }

List의 .map() 메서드처럼 값을 변형해서 가져올 수 있는 메서드가 .map()이다.



Optional 사용 시, 주의사항


1) Optional을 메서드의 매개변수로 사용하지 않기

public void someMethod(Optional<String> badArgument) { // ❌
	// ...
}

사용하면 에러가 나는 것은 아니지만, 메서드의 Input은 명확할 수록 좋기 때문에 Optional을 메서드의 매개변수로 사용하는 것은 지양...도 아니고, 하지 말자!

2) Optional을 멤버변수로 사용하지 않기

public class SomeClass {
	
    private Optional<String> badField; // ❌
    
    // ...
	
}

이 또한 이렇게 사용하다고 해서 에러가 나는 것은 아니지만, 멤버변수는 충분히 명확한 값으로 지정할 수 있으므로, 이렇게 사용하는 것은 지양...이 아니라 하니 말자!

💡 Optional은 메서드의 리턴값 또는 그 값을 받아서 처리하는 곳에서만 사용하자!

3) Optional한 원시 타입인 경우

원시 타입의 값을 Optional로 감싸야할 경우, Optional대신 아래의 클래스들을 사용할 것을 권장한다.
Optional을 사용하면서 발생할 Boxing, Unboxing 처리를 간소화해서 불필요한 리소스 낭비를 방지할 수 있다.

  • OptionalInt
  • OptionalLong
  • OptionalDouble


☕ 마무리


Java는 기본적으로 강타입 언어이고, 최대한 명확하게 사용되는 타입들을 명시해주는 것이 좋다. 그러니 Optional을 만능 도구처럼 사용하지 말고, 꼭 필요한 경우에만 적절히 사용하도록 하자.

예를 들어, DB에서 값을 조회해서 가져오는 경우, 그 결과가 어떻게 될지 확신할 수 없으므로 Optional을 사용하는 것이 적합하지만 그 이후에 처리에서 Optional을 남용하는 것은 좋지 않을 수 있다.



🙏 참고


0개의 댓글