Optional 예제로 배우기

어떤 객체가 예상치 못하게 null이 되었을 때 그 객체의 메서드를 호출하면 NullPointerException이 발생합니다. 그래서 우리는 메서드를 호출하기 전에 객체가 null인지 확인하기 위한 코드를 반복적으로 추가하게 됩니다. 똑같은 예외 처리를 반복하다 보니 좀 더 효율적인 null 처리 방법을 찾게 되었고, 이를 돕기 위한 Optional이라는 객체가 존재한다는 사실을 알게 되었습니다. Optional 예제 코드를 통해 쉽게 이해하고 적용해 보실 수 있도록 도움을 드리고자 합니다.

Optional은 Java에서 값이 존재할 수도 있고 없을 수도 있는 상황을 다룰 때 사용되는 클래스로, 값이 있는지 여부를 명시적으로 표현하고 안전하게 다룰 수 있도록 도와주는 래퍼 클래스입니다. (Java 8부터 지원)

물론 null로부터 100% 해방될 수는 없지만 Optional이 유용하게 사용될 수 있는 상황은 자주 발생합니다. null이 될 수 있는 값을 다룰 때 Optional 객체가 제공하는 메서드를 사용하면 더 읽기 쉬운 코드를 작성할 수 있고, 구현 실수를 줄일 수 있습니다. 저는 Optional을 당연하게 사용하는 자바 개발자들이 널리 널리 퍼지길 바랍니다.

Optional을 적용하기 좋은 케이스

언제 Optional을 사용하면 좋을까요? 저는 아래와 같은 상황에서 Optional을 적용했을 때 유용했습니다. 실무에서 비슷한 상황을 만났을 때 직접 적용해 보시기를 권합니다.

경험 상 null이 될 수 있는 값이 시작되는 지점에 반환 값으로 Optional을 적용하는 것이 효과적이었습니다. 그러면 그 반환 값을 사용하는 모든 곳에서는 Optional을 사용하도록 강제할 수 있기 때문에 일관성 있는 코드를 작성할 수 있습니다. 또한, 반환 값을 Optional로 감싸면 null이 될 수 있는 값이라는 것을 명시하는 것과 같은 효과를 내므로 가독성을 높여줍니다.

Optional 예제 1: DB에서 조회한 결과가 null일 수도 있는 경우 조회 메서드의 리턴 타입으로 사용

사용자를 조회하기 위해 ID를 where절 조건으로 사용할 때, 조회된 결과가 없을 수도 있습니다. 이 때 조회 결과가 null일 수 있는데 이를 Optional로 감싸서 반환합니다.

// 예시를 위한 사용자 클래스
class User {
    private int id;
    private String name;
 
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
}
 
// 사용자를 저장하고 조회하는 클래스
class UserRepository {
    private final Map<Integer, User> users = new HashMap<>();
 
    public UserRepository() {
        // 임의의 사용자 데이터를 추가합니다. 실제로는 데이터베이스나 외부 저장소에서 조회할 것입니다.
        users.put(1, new User(1, "Green"));
        users.put(2, new User(2, "Neuron"));
        users.put(3, new User(3, "Developer"));
    }
 
    public Optional<User> findUserById(int id) {
        // ID를 사용하여 사용자를 조회하고, 조회 결과가 없을 경우 Optional.empty()를 반환합니다.
        return Optional.ofNullable(users.get(id));
    }
}

위와 같이 Optional.ofNullable() 을 사용해서 findUserById 메서드의 반환 값을 감싸서 반환하면

UserRepository userRepository = new UserRepository();
// ID로 사용자 조회
int userId = 4; // 존재하지 않는 ID로 조회하는 예시
 
Optional<User> optionalUser = userRepository.findUserById(userId);
 
// Optional의 ifPresentOrElse를 사용하여 값이 존재할 경우와 없을 경우에 각각 다른 동작을 수행합니다.
optionalUser.ifPresentOrElse(
    user -> System.out.println("사용자 이름: " + user.getName()), // 값이 존재할 경우 실행
    () -> System.out.println("해당 ID의 사용자를 찾을 수 없습니다.") // 값이 존재하지 않을 경우 실행
);

userRepository를 통해 user를 조회했을 때 결과가 있는 경우와 없는 경우에 대해서 다른 동작을 해야 한다면
위와 같은 코드를 작성할 수 있습니다.
(※ Optional.ifPresentOrElse() 메서드는 Java 9부터 지원합니다.)

조회 결과가 없을 때 예외를 던지고 싶을 때는 아래와 같이 작성할 수 있습니다.
User u = userRepository.findUserById(userId).orElseThrow(() -> new RuntimeException("entity not found"));
(※ Optional.orElseThrow() 메서드는 Java 10부터 지원합니다.)

조회 결과가 있을 때 user의 getName() 메서드 반환 값으로 변환하고, 그렇지 않으면 "defaultName"을 반환하게 할 수도 있습니다.
String name = userRepository.findUserById(userId).map(o -> o.getName()).orElse("defaultName");

Optional 예제 2: 객체의 멤버 변수가 null일 수도 있는 경우 getter의 리턴 타입으로 사용

Getter 메서드의 반환 값이 null일 때 null이 아닌 값으로 변환해서 사용해야 할 때가 있습니다. 이 때 getter의 반환 값을 Optional로 감싸서 반환합니다.

public class ExampleClass {
    private String value;
 
    public ExampleClass(String value) {
        this.value = value;
    }
 
    public Optional<String> getValue() {
        // 만약 값이 null인 경우 Optional.empty()를 반환하고, 그렇지 않은 경우 Optional.of()로 값을 감싸 반환합니다.
        return Optional.ofNullable(value);
    }
}

위와 같이 getter 메서드가 null이 될 수 있는 값을 Optional로 감싸서 반환하면

ExampleClass example = new ExampleClass(null); // 값이 null인 예시 객체 생성
 
Optional<String> optionalValue = example.getValue();
 
// Optional로부터 값을 가져올 때 값이 존재하는지 확인 후 다른 값으로 변환할 수 있습니다.
String result = optionalValue.orElse("기본값");
System.out.println("변환된 값: " + result);

위 코드에서 optionalValue.orElse("기본값") 메서드 실행 시 optionalValue 내부의 값이 null이 아닌 경우 그 값을 반환하고
null인 경우에 "기본값"을 반환하게 됩니다. if문을 사용하지 않고도 null 대신 사용할 default 값을 지정해줄 수 있어서 코드의 가독성을 높이고 구현 실수를 줄일 수 있게 됩니다.

위에서 나온 Optional 예제는 실무에서 주로 사용되고 있는 패턴들을 기반으로 작성되었습니다. 여러분께서 Optional을 실무에 적용해보고 싶을 때 참고할 수 있는 자료가 되기를 바랍니다.

스스로 경험하며 얻은 깨달음을 공유하기 좋아하며, 세상이 필요로 하는 코드를 작성하기 위해 노력하는 개발자입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다