목차
어떤 객체가 예상치 못하게 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
을 실무에 적용해보고 싶을 때 참고할 수 있는 자료가 되기를 바랍니다.