JAVA

[자바] Optional

감자b 2025. 4. 3. 21:48

자바에서 null값이 없음을 나타내는 방법인데, null 참조에 대해 메서드를 호출하면 NullPointerException이 발생할 수 있다.

Optional 클래스는 null을 안전하게 처리하기 위해 도입된 기능으로 해당 클래스는 값이 존재할 수도, 없을 수도 있음을 표현한다.

 

Optional 생성 방법

Optional<String> optional = Optional.of("Hello");  // 값이 반드시 있어야 함
Optional<String> optionalEmpty = Optional.empty(); // 비어 있는 Optional
Optional<String> optionalNullable = Optional.ofNullable(null); // null 허용


 

Optional의 값을 존재 여부 확인

Optional<String> optional = Optional.of("Hello");
boolean present = optional.isPresent();
boolean isEmpty = optional.isEmpty();

 

 

Optional 값 획득*

Optional<String> optional = Optional.of("Hello");  // 값이 반드시 있어야 함
String value = optional.get(); // 값이 있는 경우 그 값을 반환하지만 값이 없다면 NoSuchElementException 발생하므로 사용에 유의해야 함!
String value1 = optional.orElse("Default Value"); // 값이 없으면 기본값 반환
String value2 = optional.orElseGet(() -> "Default Value"); // 함수형 인터페이스 활용
String value3 = optional.orElseThrow(() -> new RuntimeException("No value present")); // 값이 없으면 예외 발생
Optional<String> value4 = optional.or(() -> Optional.of("Default Value")); // 값이 있으면 해당 값의 Optional 반환, 없으면 주어진 대체 Optional 반환

 

 

Optional 값 처리 

Optional<String> optional = Optional.of("Hello");
optional.ifPresent(value -> System.out.println("Value: " + value)); // 값이 있을 경우 Consumer 실행, 없으면 아무 동작도 하지 않음
optional.ifPresentOrElse( // 값이 있다면 첫 번째 Consumer, 없다면 두 번째 Runnable을 실행
        value -> System.out.println("Value: " + value),
        () -> System.out.println("No Value Present")
);
Optional<Integer> length = optional.map(String::length); // 값이 있으면 Function<T, R> 적용 후 Optional<R> 반환, 값이 없으면 Optional.empty() 반환
Optional<Integer> result = optional.flatMap(s -> Optional.of(s.length())); // map()을 사용하면 Optional<Optional<R>>이 생성될 수도 있지만, flatMap()을 사용하면 중첩되지 않고 Optional<R>이 그대로 반환
Optional<String> filtered = optional.filter(s -> s.startsWith("H")); // 값이 있고 조건을 만족 시 그대로 반환, 조건을 만족하지 못하거나 비어있으면 Optional.empty() 반환
Stream<String> stream = optional.stream(); // 값이 있으면 단일 요소 스트림 반환, 없으면 빈 스트림 반환

 

 

map() vs flatMap() 차이점 - 중첩되지 않은 Optional을 반환

Optional<Optional<Integer>> nested = optional.map(s -> Optional.of(s.length()));
Optional<Integer> flattened = optional.flatMap(s -> Optional.of(s.length()));

orElse(), orElseGet()의 차이

위에서 Optional 생성 방법과 값을 확인하고 얻어오는 방법에 대해 간단하게 알아보았다.

이 때 orElse(), orElseGet()을 확인해보면 둘 다 값이 없으면 Default 값을 반환하는 것을 확인할 수 있다.

그런데 orElse()는 값을 인자로 받으며, orElseGet()은 Supplier<? extend T> 함수형 인터페이스를 인자로 받는데 이유가 무엇일까?

아래 예시를 통해 즉시 평가와 지연 평가에 대해 설명해보도록 하겠다.

public class Test {
    public static void main(String[] args) {
        Optional<String> optional = Optional.of("Hello");
        String result1 = optional.orElse(getDefaultValue());
        System.out.println("orElse 실행 후 Result: " + result1);
        System.out.println("-------------------------------");
        String result2 = optional.orElseGet(() -> getDefaultValue());
        System.out.println("orElseGet 실행 후 Result: " + result2);
    }
    public static String getDefaultValue() {
        System.out.println("getDefaultValue() 실행됨");
        return "Default Value";
    }
}
// getDefaultValue() 실행됨
// orElse 실행 후 Result: Hello
// -------------------------------
// orElseGet 실행 후 Result: Hello

 

일단 결과를 확인해보면 orElse()의 경우 값이 있는데도 불구하고 getDefaultValue() 메서드가 실행되었다.

하지만 orElseGet()의 경우 값이 있을 때 getDefaultValue() 메서드가 실행되지 않음을 확인할 수 있다.

이는 인자로 람다를 넘기면서 내부에서 값이 없을 때만 해당 람다를 실행하도록 하기 때문이다.

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

 

이렇게 람다를 활용하면 연산을 정의하는 시점과 실행하는 시점을 분리하는 것이 가능한데, 여기서 값이 있든 없든, 기본값을 항상 평가하는 방식을 즉시 평가라고 하며, 값이 필요한 시점에 실행을 하는 것을 지연 평가라고 한다.

따라서 orElse()는 즉시 평가, orElseGet()은 지연 평가를 활용한 방식이다.

이로 인해 orElseGet() 값이 없을 때만 연산을 실행하기 때문에, 불필요한 연산을 줄일 수 있다.

정리하면, 기본 값을 항상 사용해도 되거나 코드가 간단하다면 orElse()를 사용하고, 기본값을 생성하는 비용이 크다 orElseGet()을 효율적이다.


Best Practice

Optional메서드의 반환 값에 대해 값이 없을 수도 있다는 것을 표현하기 위해 도입되었다.

 

1. 필드나 매개변수로 쓰지 말고, 반환값으로만 사용

public class User {
    private Optional<String> name; // X

    public void process(Optional<String> name) { // X
        name.ifPresent(System.out::println);
    }
}

 

2. Optional.get() 가급적 호출하지 말 것

 get()은 값이 없을 때 예외 발생 위험이 있으므로, orElse(), orElseGet(), orElseThrow()를 사용해 안전하게 처리한다.

 

3. 기본값이 무거울 경우 orElse() 대신 orElseGet() 사용

 

4. 컬렉션이나 배열 타입을 Optional로 감싸지 말 것

 컬렉션은 자체적으로 비어있는 상태를 표현할 수 있다. (Collections.emptyList())

 이를 Optional로 감싸게 되면 이중 표현이 되고 이는 혼란을 야기할 수 있으므로 지양한다.

 

5. null 대신 Optional.empty() 반환할 것

 null을 반환하면 호출하는 쪽에서 NullPointerException 위험이 있지만 Optional.empty()를 반환하면 ifPresent(), orElse() 등으로 안전하게 처리가 가능하다.

 

Optional값이 없을 수도 있을 때 주로 사용된다. 이는 항상 값이 있어야 하는 상황이나, null이 아닌 예외가 발생해야 하는 상황에서 Optional 사용은 불필요하다.

따라서 무작정 Optional을 사용하는 경우 오히려 복잡도가 증가할 수 있으니 null이 필요한 적절한 상황에 맞추어 사용해야 한다.