- 자바 8 이전에는 메서드가 특정 조건에서 값을 반환할 수 없는 경우 두 가지 선택지가 있었다:
- 예외를 던지기: 예외는 진짜 예외적인 상황에서 사용해야 하며, 스택 추적 정보를 캡처하는 비용이 크기 때문에, 자주 사용하기에는 부적절하다.
- null 반환: null을 반환하는 경우, 메서드 호출 후 항상 null 체크를 해야 하며, 이를 누락하면 NullPointerException(NPE)을 발생시킬 위험이 크다.
- null 반환 시 클라이언트는 메서드가 null을 반환할 가능성을 염두에 두고 코드를 작성해야 한다:
if (object != null) {
return object.method();
}- 이와 같은 null 체크는 코드의 복잡성을 증가시키며, 진짜 예외 처리에 집중할 수 없게 만든다.
자바 8부터 Optional 클래스가 등장하여 null 반환 대신 사용할 수 있는 새로운 대안을 제공한다.
- Optional은 최대 1개의 원소를 담을 수 있는 불변 컬렉션으로 볼 수 있다. null 대신 비었음을 명확하게 표현할 수 있으며, 메서드 사용 시 값이 없을 수 있음을 명확히 전달한다.
null이 아닌T타입 참조 하나를 담거나 아무것도 담지 않는다.null을 피하기 위한Optional에null을 담는 것은 안티 패턴이다.
Optional<T>는 원소를 최대 1개 가질 수 있는 '불변 컬렉션' 이다.- 예외를 던지는 메서드보다 유연하고 사용하기 쉽다.
null을 반환하는 메서드보다 오류 가능성이 적다.
null을 사용한 기존 코드:
public static <E extends Comparable<E>> E max(Collection<E> c) {
// 컬렉션이 비어 있으면 예외를 던진다.
if (c.isEmpty()) {
throw new IllegalArgumentException("빈 컬렉션");
}
// 결과 값을 저장할 변수
E result = null;
// 컬렉션을 순회하며 최대값을 찾는다.
for (E e : c) {
// 현재의 결과가 null이거나 더 큰 값을 발견하면 갱신
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
// 최대값 반환 (null이 아닌 값이어야 함)
return result;
}max()메서드는 주어진 컬렉션에서 최대값을 찾는다.- 컬렉션이 비어 있으면 IllegalArgumentException을 던지며, 이는 호출자가 반드시 예외 처리를 해야 하는 부담을 준다.
- null 체크와 예외 처리가 필요하여 코드가 복잡해진다.
Optional을 사용한 코드:
public static <E extends Comparable<E>> Optional<E> max2(Collection<E> c) {
// 컬렉션이 비어 있는 경우, 빈 Optional 반환
if (c.isEmpty()) {
return Optional.empty();
}
// 결과 값을 저장할 변수
E result = null;
// 컬렉션을 순회하며 최대값을 찾는다.
for (E e : c) {
// 현재의 결과가 null이거나 더 큰 값을 발견하면 갱신
if (result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
// 최대값을 Optional로 감싸서 반환
return Optional.of(result);
}- 컬렉션이 비어 있으면
Optional.empty()를 반환하여 null 반환으로 인한 NPE 문제를 피한다. Optional.of(result)는 결과가 null이 아님을 보장한다.- Optional을 사용하면 클라이언트 코드에서 null 체크 없이 Optional의 메서드들을 통해 안전하게 값을 처리할 수 있다.
Stream API와 Optional을 이용한 코드:
public static <E extends Comparable<E>> Optional<E> max3(Collection<E> c) {
// 컬렉션의 스트림을 이용하여 최대값 찾기
return c.stream().max(Comparator.naturalOrder());
}- Stream API를 사용하여 컬렉션에서 최대값을 찾는다.
max()메서드는 Comparator를 사용하여 스트림에서 최대값을 계산하며, 결과를 Optional로 반환다.- 코드의 간결함과 가독성을 높이는 동시에, null 처리 문제도 피할 수 있다.
기본 값을 반환하는 예제:
@Test
public void optionalDefaultValue() {
List<Integer> integers = new ArrayList<>();
// 컬렉션이 비어 있으면 기본 값 0을 반환
Integer optional = max3(integers).orElse(0);
System.out.println("optional = " + optional);
}부가 설명:
orElse()메서드를 사용하여, Optional이 비어 있을 경우 기본 값을 반환한다.- 이 경우 최대값이 없으면 기본 값으로 0을 반환한다.
예외를 던지는 예제:
@Test
public void optionalDefaultThrow() {
List<Integer> integers = new ArrayList<>();
// 컬렉션이 비어 있으면 IllegalArgumentException을 던짐
Integer optional = max3(integers).orElseThrow(IllegalArgumentException::new);
System.out.println("optional = " + optional);
}부가 설명:
orElseThrow()메서드를 사용하여 값이 없을 때 예외를 던진다.- 이 경우, 빈 컬렉션이라면 IllegalArgumentException이 발생다.
값이 반드시 있다고 가정하는 예제:
@Test
public void optionalDefaultGet() {
List<Integer> integers = new ArrayList<>(List.of());
// 값이 있다고 가정하고 Optional에서 값을 가져옴
Integer optional = max3(integers).get(); // 빈 경우 예외 발생
System.out.println("optional = " + optional);
}부가 설명:
get()메서드는 값이 반드시 있다고 가정하고 사용힌다.- 값이 없을 경우
NoSuchElementException이 발생하므로 사용 시 주의
초기 생성 비용을 줄이기 위한 예제:
Connection connection = getConnection(dataSource).orElseGet(() -> getLocalConnection());부가 설명:
orElseGet()은 값이 필요할 때 지연 초기화 방식으로Supplier를 사용하여 생성한다.- 초기 생성 비용이 큰 객체를 필요할 때만 생성하도록 하여 비용을 줄일 수 있다.
- Optional 사용을 피해야 하는 경우:
- 컨테이너 타입 (예:
List,Stream, 배열)에 Optional을 사용하지 말 것.- 예를 들어,
Optional<List<T>>대신 빈 리스트를 반환하는 것이 더 간결하고 명확
- 예를 들어,
- 박싱된 기본 타입 (예:
Integer)을 Optional로 감싸지 말고, 전용 클래스 (OptionalInt,OptionalLong,OptionalDouble)를 사용할 것.- 기본 타입을 박싱하면 성능 저하가 발생하므로 전용 클래스를 사용하는 것이 더 효율적
- Optional을 컬렉션의 키, 값, 배열의 원소로 사용하지 말 것.
- Optional을 맵의 값으로 사용하면 맵에 없는 키와 값이 빈 Optional인 경우를 구분해야 하므로 복잡성이 증가
- 컨테이너 타입 (예:
- 반환값이 없을 가능성이 있는 메서드라면 Optional을 반환하는 것이 좋다.
- 이를 통해 반환값이 없을 가능성을 명확히 알리고, null 처리를 통한 NPE 위험을 줄일 수 있다.
- 그러나 Optional 반환에도 객체 생성 비용이 추가되므로, 성능이 중요한 상황에서는 null 반환이나 예외 처리가 더 적합할 수 있다.
- Optional은 반환값 이외의 용도로는 사용하지 않는 것이 권장되며, 특히 컨테이너 타입을 감싸는 것은 지양해야 한다.
Optional을 적절히 사용함으로써 코드의 안전성과 가독성을 높일 수 있지만, 사용 시 주의할 점도 많으므로 상황에 따라 신중하게 선택
 (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png)
 (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png)
 (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png)