목차
1. Optional이란?
Java 8에서 도입된 Optional는 null이 될 수 있는 값을 감싸는 래퍼 클래스입니다. NullPointerException(NPE)를 방지하고 null 체크 로직을 더 간결하고 안전하게 작성할 수 있게 해줍니다.
1.1 Optional 도입 배경 🎯
// 전통적인 null 체크 방식
public String getUpperCaseUserName(User user) {
if (user == null) {
return "UNKNOWN";
}
String name = user.getName();
if (name == null) {
return "UNKNOWN";
}
return name.toUpperCase();
}
// Optional을 사용한 방식
public String getUpperCaseUserName(Optional<User> userOpt) {
return userOpt
.map(User::getName)
.map(String::toUpperCase)
.orElse("UNKNOWN");
}
2. Optional의 생성과 기본 사용법
2.1 Optional 객체 생성하기 🛠️
// 비어있는 Optional 생성
Optional<String> empty = Optional.empty();
// null이 아닌 값으로 Optional 생성
String name = "John";
Optional<String> opt = Optional.of(name);
// null일 수 있는 값으로 Optional 생성
String nullableName = null;
Optional<String> optNullable = Optional.ofNullable(nullableName);
2.2 값 존재 여부 확인 🔍
Optional<String> opt = Optional.of("Hello");
// isPresent() 사용
if (opt.isPresent()) {
System.out.println("값이 존재함: " + opt.get());
}
// isEmpty() 사용 (Java 11부터)
if (opt.isEmpty()) {
System.out.println("값이 없음");
}
3. Optional 메소드 상세 가이드
3.1 값 추출 메소드 📤
Optional<String> opt = Optional.of("Hello");
// get() - 값이 없으면 NoSuchElementException 발생
String value = opt.get();
// orElse() - 값이 없으면 기본값 반환
String valueOrDefault = opt.orElse("Default");
// orElseGet() - 값이 없을 때만 Supplier 실행
String valueOrComputed = opt.orElseGet(() -> computeValue());
// orElseThrow() - 값이 없으면 지정된 예외 발생
String valueOrThrow = opt.orElseThrow(() ->
new IllegalStateException("값이 없습니다."));
3.2 orElse()와 orElseGet()의 중요한 차이점 ⚠️
public class OrElseExample {
public static void main(String[] args) {
Optional<String> opt = Optional.of("Hello");
// orElse는 항상 createExpensiveDefault()를 실행
String value1 = opt.orElse(createExpensiveDefault());
// orElseGet은 값이 없을 때만 Supplier를 실행
String value2 = opt.orElseGet(() -> createExpensiveDefault());
}
private static String createExpensiveDefault() {
System.out.println("비용이 큰 기본값 생성...");
return "Default";
}
}
4. 실전 활용 패턴과 베스트 프랙티스
4.1 체이닝을 통한 복잡한 로직 처리 🔗
public class UserService {
public Optional<String> getUserEmailByUsername(String username) {
return Optional.ofNullable(findUser(username))
.map(User::getEmail)
.filter(email -> email.contains("@"))
.map(String::toLowerCase);
}
}
4.2 컬렉션과 함께 사용하기 📚
public class CollectionWithOptional {
public static void main(String[] args) {
List<Optional<String>> listOfOptionals = Arrays.asList(
Optional.of("First"),
Optional.empty(),
Optional.of("Third")
);
// Optional 값만 추출하여 새로운 리스트 생성
List<String> actualValues = listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
}
5. 성능과 주의사항
5.1 Optional 사용 시 주의점 ⚠️
public class OptionalDontDo {
// ❌ 잘못된 사용: Optional을 필드로 사용
private Optional<String> name; // 직렬화 문제 발생 가능
// ✅ 올바른 사용: 반환 타입으로 사용
public Optional<String> getName() {
return Optional.ofNullable(name);
}
// ❌ 잘못된 사용: Optional을 메소드 매개변수로 사용
public void setName(Optional<String> name) {
this.name = name.orElse(null);
}
// ✅ 올바른 사용: null 허용 여부를 명시적으로 표현
public void setName(@Nullable String name) {
this.name = name;
}
}
5.2 성능 최적화 팁 🚀
public class OptionalPerformance {
// 1. 불필요한 Optional 생성 피하기
public Optional<User> findUserById(Long id) {
User user = userRepository.findById(id);
// ❌ 불필요: Optional.ofNullable(Optional.ofNullable(user))
// ✅ 올바름: Optional.ofNullable(user)
return Optional.ofNullable(user);
}
// 2. 반복문에서 Optional 사용 최적화
public List<String> processUsers(List<User> users) {
return users.stream()
.map(User::getEmail)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
}
6. 실제 프로젝트 적용 사례
6.1 실제 비즈니스 로직에서의 활용 💼
@Service
public class OrderService {
private final UserRepository userRepository;
private final OrderRepository orderRepository;
public Optional<OrderSummary> getLatestOrderSummary(String username) {
return Optional.ofNullable(username)
.flatMap(userRepository::findByUsername)
.flatMap(user -> orderRepository.findLatestOrder(user.getId()))
.map(order -> new OrderSummary(
order.getId(),
order.getTotal(),
order.getStatus()
));
}
}
6.2 테스트 코드 작성 🧪
@Test
public void whenUserExists_thenReturnOrder() {
// Given
String username = "john.doe";
User user = new User(1L, username);
Order order = new Order(1L, 100.0, "COMPLETED");
when(userRepository.findByUsername(username))
.thenReturn(Optional.of(user));
when(orderRepository.findLatestOrder(user.getId()))
.thenReturn(Optional.of(order));
// When
Optional<OrderSummary> result =
orderService.getLatestOrderSummary(username);
// Then
assertTrue(result.isPresent());
assertEquals(100.0, result.get().getTotal(), 0.01);
}
결론 📝
Optional은 null 처리를 위한 강력한 도구이지만, 올바른 사용법과 패턴을 이해하는 것이 중요합니다.
주요 포인트를 정리하면:
- Optional은 반환 값으로 사용하는 것이 가장 이상적
- orElse()와 orElseGet()의 차이를 이해하고 적절히 사용
- 체이닝을 통한 우아한 코드 작성 가능
- 불필요한 Optional 생성은 피하기
- 테스트 가능성을 높이는 도구로 활용
Optional을 효과적으로 활용하면 더 안전하고 유지보수하기 좋은 코드를 작성할 수 있습니다.
📚 추가 학습 자료
- Java Optional API 문서
- Effective Java 항목 55
- Modern Java in Action 책의 Optional 챕터
이 가이드가 여러분의 null 처리 코드를 더 우아하게 만드는 데 도움이 되길 바랍니다!
질문이나 제안사항은 댓글로 남겨주세요. 🙋♂️
'개발일지' 카테고리의 다른 글
Java 개발일지: CompletableFuture로 구현하는 비동기 프로그래밍 여정 📘 (0) | 2024.12.10 |
---|---|
Java Records: 현대적 데이터 클래스의 새로운 패러다임 📝 (0) | 2024.12.10 |
Java Stream API 마스터하기: 함수형 프로그래밍의 강력함 🌊 (0) | 2024.12.10 |
Java String Pool의 내부 동작 원리: 메모리 최적화와 문자열 관리 심층 분석 📘 (0) | 2024.12.10 |
JavaScript Set과 Map: 데이터를 효율적으로 다루는 방법 (0) | 2024.12.02 |