Optional 남용하다가 성능 터진 썰 (feat. Java 실무 경험)
실제 실무 프로젝트 중, 전자정부프레임워크 기반의 레거시 시스템을 개선하면서 Optional을 적극 도입한 적이 있었다. NullPointerException 방지 목적이었고, 나름 Functional 스타일도 도입하고 싶어서였다. 그런데 코드 리뷰 중 “Optional을 왜 필드로 선언했냐”는 지적을 받았고, 성능 측정 결과 오히려 처리 속도가 느려지고 메모리 낭비도 발생했다. Optional은 제대로 사용하면 확실히 좋지만, 잘못 쓰면 차라리 null보다 못한 결과를 낳는다.
이번 글에서는 Java 8 이후 도입된 Optional의 정확한 개념, 쓰면 안 되는 패턴, 그리고 실무에서 자주 쓰는 올바른 방식을 정리해보겠다.
Optional이란 무엇인가?
Optional<T>은 값이 있을 수도 있고 없을 수도 있는 객체를 표현하기 위한 컨테이너다. 즉, null 대신 Optional을 쓰면 컴파일 단계에서부터 ‘존재 여부’를 명확히 표현할 수 있어, NullPointerException을 줄이는 데 도움이 된다.
예:
Optional<String> name = Optional.of("홍길동");
System.out.println(name.get()); // 홍길동
값이 없을 수도 있다면 Optional.empty() 또는 Optional.ofNullable(value)을 사용한다.
Optional, 이렇게 쓰면 안 됩니다 (실무에서 자주 나오는 잘못된 예)
1. DTO나 Entity 필드에 Optional 사용
// ❌ 잘못된 예시
public class User {
private Optional<String> name;
private Optional<Integer> age;
}
이건 절대 하면 안 된다. Optional은 필드가 아니라 메서드 반환값에만 쓰는 게 권장된다. 필드에 Optional이 있으면 직렬화, JPA, Jackson과의 호환성 문제가 발생한다.
2. Optional.get()을 바로 호출
// ❌ 위험한 예시
Optional<User> user = Optional.empty();
User u = user.get(); // NoSuchElementException 발생
Optional을 쓰는 이유는 null 체크를 안전하게 하기 위함인데, get()을 바로 쓰면 의미가 없다.
Optional의 올바른 사용법: 실무 기준 정리
1. Optional은 반환값으로만 사용하자
public Optional<User> findUserById(String id) {
return Optional.ofNullable(userRepository.findById(id));
}
값이 없을 수도 있는 메서드의 반환값으로 Optional을 사용하면, 호출 측에서 처리 흐름이 명확해진다.
2. 기본값 처리: orElse, orElseGet
User user = findUserById("hong").orElse(new User("게스트"));
혹은 지연 생성이 필요한 경우:
User user = findUserById("hong").orElseGet(() -> createGuestUser());
3. 조건부 실행: ifPresent / ifPresentOrElse
findUserById("hong")
.ifPresent(user -> System.out.println("이름: " + user.getName()));
Java 9 이상에선 ifPresentOrElse도 사용 가능:
findUserById("hong")
.ifPresentOrElse(
user -> System.out.println("회원: " + user.getName()),
() -> System.out.println("비회원")
);
4. 조건 필터링: filter
Optional<User> adult = findUserById("hong")
.filter(user -> user.getAge() >= 20);
filter는 조건이 true일 때만 값을 유지하고, false면 Optional.empty()가 된다.
5. 값 변환: map / flatMap
String userName = findUserById("hong")
.map(User::getName)
.orElse("이름 없음");
중첩된 Optional을 다룰 땐 flatMap을 활용:
Optional<Address> address = findUserById("hong")
.flatMap(User::getAddress);
실무 예제: 로그인 사용자 정보 조회
Spring Security에서 현재 로그인한 사용자 정보를 가져올 때 Optional을 유용하게 사용할 수 있다.
public Optional<User> getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !(auth.getPrincipal() instanceof UserDetails)) {
return Optional.empty();
}
UserDetails userDetails = (UserDetails) auth.getPrincipal();
return Optional.ofNullable(userService.findByUsername(userDetails.getUsername()));
}
이렇게 하면 호출부는 다음처럼 처리 가능:
getCurrentUser().ifPresent(user -> {
log.info("현재 로그인한 사용자: {}", user.getName());
});
Optional vs null 비교 정리
| 항목 | Optional | null |
|---|---|---|
| 의도 표현 | 값의 유무를 명시 | 함축적 (명시적 아님) |
| 실수 방지 | 컴파일 타임에서 에러 줄임 | NullPointerException 발생 가능 |
| 성능 | 약간 느릴 수 있음 | 빠름 |
| 사용 위치 | 반환값에 한정 | 모든 곳 |
마무리: Optional, 잘 쓰면 득이고 잘못 쓰면 독
Optional은 분명 Java에 필요한 기능이다. 특히 값이 없을 수 있다는 걸 명확하게 표현하고, null-safe한 코드를 작성할 수 있게 해준다. 하지만 DTO나 Entity 필드에 넣는다거나, Optional.get()을 무심코 사용하는 경우엔 null보다 위험할 수 있다.
이 글이 Optional을 실무에서 어떻게 활용하고, 어디까지 쓰는 게 좋은지 판단하는 데 도움이 되었길 바란다. 다음 코드 작성부터는 Optional을 '기능'이 아닌 '약속'으로 써보자.
'웹개발 > Java' 카테고리의 다른 글
| Java HashMap vs Hashtable vs ConcurrentHashMap: 동시성과 성능의 완벽 가이드 (4) | 2025.07.21 |
|---|---|
| Java Stream API 정렬, 실무에서는 이렇게 씁니다 (Comparable, Comparator 완벽 정리) (1) | 2025.07.20 |
| 자바 equals와 ==의 차이 정확히 알고 계신가요? (실무에서 자주 하는 실수 예시 포함) (2) | 2025.07.09 |
| 자바 static 키워드 제대로 알고 써보자 (사용법, 주의사항 총정리) (4) | 2025.07.09 |
| 『Java 날짜 포맷』 SimpleDateFormat 대신 DateTimeFormatter 써야 하는 이유 (코드 포함) (2) | 2025.07.08 |