Java Category/Java

[Java] Optional<T>

ReBugs 2024. 3. 15.

자바에서 Optional 클래스는 null이 될 수 있는 객체를 감싸는 래퍼 클래스이다. java.util.Optional<T>는 자바 8에서 도입되었으며, NullPointerException을 방지하고, 명시적으로 변수가 null 값을 가질 수 있음을 표현할 수 있는 방법을 제공한다. 이를 통해 개발자는 보다 깔끔하고 의도가 명확한 코드를 작성할 수 있다.

 

기본 사용법

Optional 객체를 생성하는 기본적인 방법은 Optional.of(value), Optional.ofNullable(value), 그리고 Optional.empty() 세 가지가 있다.

 

  • Optional.of(value): null이 아닌 명시적인 값을 가지는 Optional 객체를 반환한다. 만약 인자로 넘긴 값이 null이라면, 즉시 NullPointerException을 발생시킨다.
  • Optional.ofNullable(value): 인자로 넘긴 값이 null일 수도 있는 경우 사용한다. 값이 null이면 빈 Optional 객체를 반환한다.
  • Optional.empty(): 빈 Optional 객체를 생성한다.
Optional<String> optional = Optional.of("hello");

// 값이 존재하는 경우에만 처리를 실행
optional.ifPresent(value -> System.out.println(value));

// 값이 존재하지 않는 경우 기본값을 제공
String valueOrDefault = optional.orElse("default");
System.out.println(valueOrDefault);

// 값이 존재하지 않을 때 예외를 던짐
String valueOrThrow = optional.orElseThrow(IllegalStateException::new);
System.out.println(valueOrThrow);

// 값이 존재할 때 변환을 수행
Optional<Integer> length = optional.map(String::length);
System.out.println(length.orElse(0));

 

아래의 예제에서는 간단한 사용자 정보를 나타내는 User 클래스를 만들고, 이를 Optional로 감싸서 처리하는 방법을 보여준다. 이를 통해 Optional의 기본적인 사용 방법과 Optional을 사용할 때의 장점을 이해할 수 있다.

public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}

 

Optional을 사용하여 객체를 감싸는 예제

이 예제에서는 Optional.ofNullable() 메소드를 사용하여 null일 수도 있는 User 객체를 Optional로 감싸고, ifPresent() 메소드를 사용하여 User 객체가 실제로 존재할 때만 특정 작업을 수행하도록 한다.

public class OptionalExample {
    public static void main(String[] args) {
        // User 객체가 존재하는 경우
        User user = new User("John Doe", 30);
        Optional<User> optionalUser = Optional.ofNullable(user);

        optionalUser.ifPresent(u -> System.out.println("이름: " + u.getName() + ", 나이: " + u.getAge()));

        // User 객체가 null인 경우
        User nullUser = null;
        Optional<User> emptyOptional = Optional.ofNullable(nullUser);

        // User 객체가 없을 때 기본값을 제공
        User defaultUser = emptyOptional.orElse(new User("기본 사용자", 0));
        System.out.println("이름: " + defaultUser.getName() + ", 나이: " + defaultUser.getAge());

        // User 객체가 없을 때 예외를 던지거나 특정 행동을 하기
        emptyOptional.orElseThrow(() -> new IllegalStateException("User 객체가 없습니다."));
    }
}

이 예제에서는 Optional을 사용하여 null 가능성이 있는 User 객체를 안전하게 처리한다.

ifPresent() 메소드를 사용하여 User 객체가 존재하는 경우에만 작업을 수행하고,

orElse() 메소드를 사용하여 User 객체가 없는 경우 기본값을 제공한다.

orElseThrow() 메소드를 사용하여 User 객체가 없을 때 예외를 던질 수도 있다.

ifPresent(Consumer<? super T> action) 메소드

Optional 객체가 값을 갖고 있을 경우, 즉 값이 비어 있지 않을 경우에 주어진 액션(람다 표현식 또는 메소드 참조)을 실행한다. 값이 없을 경우에는 아무런 동작도 하지 않는다. 이 메소드는 주로 Optional 객체가 값이 있을 때만 특정 작업을 실행하고 싶을 때 사용된다.

 

orElse(T other) 메소드

Optional 객체가 값이 있으면 그 값을 반환하고, 값이 없을 경우에는 메소드에 전달된 인자를 기본값으로 반환한다. 이 메소드는 Optional 객체가 비어 있을 때의 기본값을 제공하고자 할 때 유용하게 사용된다.

 

get() 메소드 대신 orElse() 또는 orElseGet()을 사용하는 이유

get() 메소드가 Optional 객체가 값을 가지고 있지 않을 때, 즉 Optional 객체가 비어 있을 때 NoSuchElementException을 발생시키기 때문이다. 이는 Optional을 사용하는 주된 목적 중 하나인 안전한 null 처리를 위배한다.

 

orElse() 메소드는 Optional 객체가 비어 있을 때 제공할 기본값을 반환한다. 이 메소드는 Optional 객체의 값이 있든 없든 항상 기본값을 평가하기 때문에, 기본값 생성 비용이 높은 경우에는 비효율적일 수 있다.

 

orElseGet() 메소드는 Optional 객체가 비어 있을 때만 제공할 기본값을 생성하기 위해 사용할 Supplier 함수형 인터페이스를 인자로 받는다. 이 방법은 Optional 객체가 비어 있을 때만 기본값을 생성하기 때문에, orElse()에 비해 기본값 생성 비용이 높은 경우에 효율적이다.

 

간단히 말해서, orElse()와 orElseGet()은 Optional 객체가 비어 있을 때 안전하게 기본값을 제공하며, orElseGet()은 필요할 때만 기본값을 생성하는 더 효율적인 방법을 제공한다. 따라서 get() 메소드의 사용은 값의 존재를 명시적으로 확인한 후에만 사용해야 하며, 그렇지 않은 경우 orElse() 또는 orElseGet() 같은 보다 안전한 대안을 사용하는 것이 좋다.

 

null이 아닌, 비어 있는 Optional 객체를 생성하는 방법

Optional을 사용할 때 null이 아닌, 비어 있는 Optional 객체를 생성하는 방법은 Optional.empty() 메소드를 사용하는 것이다. 

이 메소드는 값을 포함하지 않는, 즉 비어 있는 Optional 객체를 반환한다. 이 방법은 반환 값이 없음을 명시적으로 표현할 때 유용하며, 메소드의 시그니처를 통해 반환 값이 있을 수도 있고 없을 수도 있음을 명확하게 나타낼 수 있다.

 

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

이 코드는 비어 있는 Optional 객체를 생성한다. 이 객체는 아무런 값도 포함하지 않으며, isPresent() 메소드를 호출했을 때 false를 반환한다.

Optional.empty()를 사용하는 상황의 예로는, 어떤 조건에 따라 값이 반환될 수도 있고 반환되지 않을 수도 있는 메소드에서, 조건에 맞지 않아 값이 반환되지 않는 경우에 사용할 수 있다.

public Optional<String> findUserNameById(Long userId) {
    // 사용자 ID로 사용자 이름을 조회하는 로직
    // 만약 해당 ID의 사용자가 없다면, 비어 있는 Optional을 반환
    return Optional.empty();
}

이렇게 Optional.empty()를 사용하면, 메소드의 반환 값이 항상 Optional 타입임을 보장할 수 있으며, 호출한 곳에서는 반환된 Optional 객체를 통해 값이 존재하는지 여부를 안전하게 확인하고 처리할 수 있다.

 

이 방식은 null 반환을 피하고, NullPointerException의 위험을 줄이며, 코드의 가독성과 안정성을 높이는 데 도움이 된다.

 

주의할 점

Optional은 반환 타입, 매개 변수, 필드 타입으로 사용할 수 있으나, 특히 API 설계 시에는 신중하게 사용해야 한다.

Optional을 매개 변수로 사용하는 것은 권장되지 않으며, 필드 타입으로 사용할 때도 주의가 필요하다.

또한, Optional은 Serializable 인터페이스를 구현하지 않기 때문에, 직렬화가 필요한 상황에서는 사용에 제약이 따른다.

댓글