우당탕탕
Java Optional 제대로 쓰는 법과 흔한 안티패턴 사례 비교 본문
제가 Java Optional을 처음 접했을 때, 굉장히 편리하다고 생각했는데 막상 프로젝트에 적용하면서 왜 이렇게 복잡하고 헷갈리는지 한참 삽질했어요. 그래서 이번에 제대로 Optional을 쓰면서 겪은 문제와 그때 참고했던 다른 사례들을 비교해봤습니다.
이 글에서는 Java Optional을 어떤 상황에 쓰는 게 맞는지, 흔히 하는 안티패턴은 무엇인지, 그리고 타사례와 비교해 보며 어떻게 하면 깔끔하고 안전하게 코드를 작성할 수 있을지 차근차근 알려드릴게요.
개발 환경 / 버전 정보
프로젝트는 Java 17 기반이고 Spring Boot 3.1을 사용하고 있습니다. Optional은 Java 8부터 정식 지원됐지만, 최신 버전에서 쓰는 팁 위주로 설명드릴게요.
Java Optional 제대로 쓰는 법과 안티패턴 관련 정보
Optional 제대로 써야 하는 이유와 기본 개념
사실 이 부분이 Optional에서 가장 헷갈릴 수 있는데요, Optional의 핵심은 NullPointerException을 줄이고 명시적으로 값의 존재 여부를 표현하는 것입니다. 근데 저도 처음엔 이걸 "그냥 null 대신 감싸면 되는구나" 정도로만 이해해서, 어디에 어떻게 써야 할지 감이 안 왔어요.
Optional은 반환 타입으로 주로 씁니다. 메서드가 값을 반환할 때 '값이 없으면 null' 대신 Optional을 씌워주면, 호출하는 쪽에서 안전하게 처리할 수 있거든요. 하지만 필드 타입이나 파라미터 타입에 쓰는 건 권장하지 않는 방법이에요. 그렇지 않으면 오히려 코드가 더 복잡해질 수 있거든요.
제가 사용한 방법 vs 다른 사람이 쓴 안티패턴 비교
제가 처음 Optional을 적용했을 때, 아래와 같은 코드를 짰는데요. 문제는 Optional을 메서드 파라미터에 쓰고, 내부에서 또 불필요하게 null 체크까지 겹쳤다는 점이었어요.
// 저의 초기 코드 예시: Optional을 파라미터로 받음
public void processData(Optional optName) {
if (optName != null && optName.isPresent()) {
System.out.println("Name: " + optName.get());
} else {
System.out.println("이름이 없어요.");
}
}
이 코드는 Optional 파라미터가 null일 수도 있다고 가정하고 체크하는데, 사실 Optional은 내부에서 null이 아닌 값의 유무를 감싸는 것이기 때문에 Optional 그 자체가 null이면 안 됩니다. 즉, Optional을 파라미터로 받는 건 안티패턴이에요.
반면에, 다른 개발자들이 많이 쓰는 안티패턴을 살펴보면 이런 경우가 많더라고요.
- Optional을 필드로 선언해놓고, 외부에서 null 체크를 전혀 안 함
- Optional 내부 값을 꺼낼 때 무조건
get()만 호출, 존재 유무 체크 안 함 - Optional 생성 시
Optional.of(null)처럼 null이 들어갈 수 있는 방식으로 생성
이런 경우는 자칫하면 NPE를 초래하거나 Optional의 장점을 무색하게 만들죠.
Java Optional 제대로 쓰는 법과 안티패턴 관련 정보
이렇게 하면 됩니다: 제가 정리한 올바른 Optional 활용법
먼저 Optional은 반환 타입으로만 쓰는 게 제일 깔끔해요. 그리고 null 대신 Optional.empty()를 꼭 사용해야 합니다.
public Optional findNameById(Long id) {
String name = database.queryName(id); // null일 수도 있음
return Optional.ofNullable(name); // null 허용 시 ofNullable 사용
}
// 호출 측
Optional optName = findNameById(10L);
optName.ifPresent(name -> System.out.println("Name: " + name));
이렇게 하면 호출하는 쪽에서 isPresent()나 ifPresent() 등을 이용해 명확히 처리할 수 있어요. 저는 orElseThrow()를 써서 값이 없을 때 바로 예외를 던지는 방식도 자주 씁니다.
Optional 필드로 쓰는 건 꼭 피하세요. 자바 공식 문서에서도 Optional은 필드나 파라미터 타입으로 쓰지 말라고 명시되어 있습니다.
제가 겪은 삽질 사례와 타 개발자들 경험 비교
한번은 Optional을 메서드 파라미터로 받고 내부에 orElse(null)를 써서 또 null 처리를 하는 복합적인 코드를 작성했는데요, 이렇게 하면 Optional의 장점이 사라지는 거였어요. 제가 작성한 예시는 다음과 같습니다.
public void handle(Optional optValue) {
String value = optValue.orElse(null);
if (value != null) {
System.out.println(value);
} else {
System.out.println("값 없음");
}
}
반면에 주변 다른 개발자들은 Optional은 오직 반환 값으로만 쓰고, 이렇게 명확한 API 설계를 권장하더라고요. 그래서 저는 지금은 이렇게 개선했습니다.
public void handle(String value) {
if (value != null) {
System.out.println(value);
} else {
System.out.println("값 없음");
}
}
public Optional getValue() {
return Optional.ofNullable(this.value);
}
결국 Optional은 사용법이 명확해야지, 사용 위치나 의도에 맞지 않으면 오히려 복잡해지고 헷갈려요.
Java Optional 제대로 쓰는 법과 안티패턴 관련 정보
자주 하는 실수와 이런 부분에서 많이 틀립니다
- Optional 생성 시 null 직접 전달:
Optional.of(null)은 바로 NPE, 대신Optional.ofNullable()을 써야 합니다. - 필드 타입으로 Optional 선언: 자바 가이드라인상 비추천, 필요하면 그냥 null 처리하거나 별도 래퍼 클래스 사용.
- Optional.get()을 바로 호출: 값이 없는 경우 NoSuchElementException 발생하니 꼭
isPresent()나ifPresent()로 체크.
심화: 스트림과 Optional 결합해서 더 깔끔하게
그런데 여기서 많이들 헷갈려하는 게 Optional과 스트림을 같이 쓸 때인데요, 저도 처음엔 Optional을 스트림처럼 처리하려다 삽질 많았어요. 예를 들어, Optional 내부 값이 있으면 매핑하고, 없으면 빈 스트림처럼 처리하는 방법이 있습니다.
// Optional을 Stream으로 변환하는 깔끔한 방법
Optional optional = Optional.ofNullable("hello");
List result = optional.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result); // [HELLO]
이 방식은 Optional을 스트림처럼 다룰 수 있어서, 여러 Optional값을 합쳐서 처리할 때 매우 유용하더라고요.
자주 물어보시는 것들
Q. Optional을 필드로 쓰면 왜 안 되나요?
A. Optional은 API 반환값으로 명확한 의도를 표현하는 것이라, 필드로 쓰면 불필요한 래핑이 계속 발생해 성능 저하와 코드 복잡도 상승을 초래합니다. 그리고 직렬화/역직렬화나 ORM 매핑에서 문제도 생겨요.
Q. Optional 내부 값 꺼낼 때 무조건 get()만 써도 되나요?
A. 절대 안 됩니다. 값이 없으면 NoSuchElementException이 떨어져서요. 반드시 isPresent()나 ifPresent()를 써서 값 존재 여부를 먼저 체크해야 해요.
Q. null과 Optional, 둘 중 어떤 걸 더 선호하나요?
A. 반환하는 API에서는 Optional을 선호합니다. null이 섞이면 NPE 위험이 커서요. 다만 내부 로직이나 필드에서는 null 체크로 충분한 경우가 많습니다.
정리하자면, Optional은 반환 타입에 명확한 의도를 넣는 용도로 깔끔하게 쓰고, 그 외에는 null 체크를 적절히 병행하는 게 가장 무난합니다. 저도 이런 구조로 바꾸고 나서 코드가 훨씬 읽기 쉽고 안전해졌습니다.
필드 타입으로 쓰는 사례나 파라미터로 쓰는 사례는 자칫 코드 복잡도와 버그를 키우는 안티패턴임을 꼭 기억하세요. 그리고 스트림과 결합하면 Optional을 더 쓸모 있게 활용할 수 있습니다.
'언어 > Java' 카테고리의 다른 글
| Java 예외 처리, Checked와 Unchecked를 실제로 언제 써야 할까 (0) | 2026.09.03 |
|---|---|
| Java 17에서 실무에 바로 써먹는 새로운 기능들 체크리스트 (0) | 2026.08.27 |
| Java 예외 처리에서 Checked와 Unchecked, 비용 차이로 이해하기 (0) | 2026.07.30 |
| Java 17 새로운 기능, 실무에 바로 적용하는 절차 중심 가이드 (0) | 2026.07.23 |
| Java 동시성 컬렉션 ConcurrentHashMap 실제 사용하며 겪은 문제들 (0) | 2026.07.23 |
