Java Optional
Optional이란?
Java 8에서 도입된 Optional<T>는 null이 될 수 있는 값을 감싸는 컨테이너 객체입니다. NullPointerException을 방지하고 명시적으로 값의 부재를 표현할 수 있습니다.
Optional 생성
// 빈 Optional
Optional<String> empty = Optional.empty();
// null이 아닌 값으로 생성
Optional<String> of = Optional.of("Hello");
// null일 수 있는 값으로 생성
Optional<String> nullable = Optional.ofNullable(possiblyNull);
⚠️ 주의: Optional.of(null)은 NullPointerException을 발생시킵니다.
값 확인 및 추출
기본 메서드
optional.isPresent() // 값이 있으면 true
optional.isEmpty() // 값이 없으면 true (Java 11+)
optional.get() // 값 반환 (없으면 NoSuchElementException)
안전한 값 추출
// 기본값 제공
String result = optional.orElse("default");
// 기본값을 Supplier로 제공 (lazy evaluation)
String result = optional.orElseGet(() -> getDefaultValue());
// 예외 던지기
String result = optional.orElseThrow();
String result = optional.orElseThrow(() -> new CustomException());
함수형 메서드
ifPresent / ifPresentOrElse
// 값이 있을 때만 실행
optional.ifPresent(value -> System.out.println(value));
// Java 9+: 값이 있을 때와 없을 때 각각 처리
optional.ifPresentOrElse(
value -> System.out.println(value),
() -> System.out.println("No value")
);
map
Optional<String> name = Optional.of("John");
Optional<Integer> length = name.map(String::length);
// 체이닝
Optional<String> upper = name
.map(String::trim)
.map(String::toUpperCase);
flatMap
중첩된 Optional을 평탄화할 때 사용:
public Optional<Address> getAddress(User user) {
return Optional.ofNullable(user)
.flatMap(User::getAddress);
}
// User 클래스
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
filter
Optional<String> filtered = optional
.filter(s -> s.length() > 5)
.filter(s -> s.startsWith("A"));
베스트 프랙티스
✅ DO (권장사항)
1. 반환 타입으로만 사용
public Optional<User> findUserById(Long id) {
return userRepository.findById(id);
}
2. orElse vs orElseGet 올바르게 선택
// ❌ 매번 새 객체 생성
result.orElse(new User());
// ✅ 필요할 때만 생성
result.orElseGet(() -> new User());
3. ifPresent 대신 함수형 메서드 활용
// ❌ 절차적 스타일
if (optional.isPresent()) {
String value = optional.get();
System.out.println(value.toUpperCase());
}
// ✅ 함수형 스타일
optional.map(String::toUpperCase)
.ifPresent(System.out::println);
4. Optional 체이닝으로 null 체크 제거
// ❌ 전통적인 null 체크
String city = null;
if (user != null && user.getAddress() != null) {
city = user.getAddress().getCity();
}
// ✅ Optional 체이닝
String city = Optional.ofNullable(user)
.flatMap(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
❌ DON’T (안티패턴)
1. 필드로 사용하지 말 것
// ❌ 직렬화 문제, 메모리 오버헤드
public class User {
private Optional<String> middleName;
}
// ✅ null 허용
public class User {
private String middleName; // can be null
}
2. 메서드 파라미터로 사용하지 말 것
// ❌ 불필요한 복잡성
public void updateUser(Optional<User> user) { }
// ✅ 오버로딩 또는 null 허용
public void updateUser(User user) { }
3. 컬렉션을 Optional로 감싸지 말 것
// ❌ 불필요한 래핑
public Optional<List<User>> getUsers() {
return Optional.ofNullable(users);
}
// ✅ 빈 컬렉션 반환
public List<User> getUsers() {
return users != null ? users : Collections.emptyList();
}
4. get() 직접 호출 지양
// ❌ NoSuchElementException 위험
String value = optional.get();
// ✅ 안전한 대안 사용
String value = optional.orElse("default");
String value = optional.orElseThrow(() -> new CustomException());
5. isPresent() + get() 조합 지양
// ❌ Optional의 의미 없음
if (optional.isPresent()) {
return optional.get();
} else {
return defaultValue;
}
// ✅ 메서드 활용
return optional.orElse(defaultValue);
성능 최적화 팁
// ❌ 비효율: 매번 새 객체 생성
return optional.orElse(new ExpensiveObject());
// ✅ 효율: 필요할 때만 생성
return optional.orElseGet(() -> new ExpensiveObject());
// ❌ 비효율: 불필요한 Optional 생성
public Optional<String> getName() {
return Optional.of(this.name); // name이 항상 non-null이면 불필요
}
// ✅ 효율: null 가능성이 있을 때만 사용
public String getName() {
return this.name; // null 가능하면 호출자가 처리
}
// Optional 캐싱 (불변 상수)
private static final Optional<String> EMPTY_STRING = Optional.empty();
성능 고려사항
- Optional은 객체 래핑으로 인한 약간의 오버헤드가 있습니다
- 성능이 매우 중요한 루프나 스트림 처리에서는 null 체크가 더 효율적일 수 있습니다
- 하지만 대부분의 비즈니스 로직에서는 성능 차이가 미미하며, 가독성과 안정성 이점이 더 큽니다
Java 9+ 추가 기능
// or(): 대체 Optional 제공
Optional<String> result = optional.or(() -> Optional.of("alternative"));
// stream(): Optional을 Stream으로 변환
List<String> list = optionalList.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
// isEmpty(): 값이 없는지 확인 (Java 11+)
if (optional.isEmpty()) {
// 값이 없을 때 처리
}
요약
Optional은 null 처리를 명시적이고 안전하게 만드는 강력한 도구입니다.
핵심 원칙
- 반환 타입으로 사용 - 메서드의 결과가 없을 수 있음을 명시
- 함수형 메서드 활용 - map, flatMap, filter로 체이닝
- 필드나 파라미터로 사용 금지 - 불필요한 복잡성 증가
- orElseGet 선호 - 성능 최적화
- get() 직접 호출 지양 - 안전한 대안 사용