이펙티브 자바, 쉽게 정리하기 - item 55. 옵셔널 반환은 신중히 하라
null
처리에 관한 문제
- 해당 객체의 메서드가
null
을 반환할지 알 수 없다.- 클라이언트는 구현 코드를 자세히 살펴보고
null
을 처리해야 했다.
- 클라이언트는 구현 코드를 자세히 살펴보고
자바8 이전 null
처리 방법
if (object != null) {
return object.method();
}
throw new NullPointerException();
if (result != null)
throw new NullPointerException();
코드가 더러워지거나, 진짜 예외의 처리에 쓰여야 할 예외가 널 처리에 쓰이게 되어버린다.
Optional<T>
의 등장
null
이 아닌T
타입 참조 하나를 담거나 아무것도 담지 않는다.null
을 피하기 위한Optional
에null
을 담는 것은 안티 패턴이다.
Optional<T>
는 원소를 최대 1개 가질 수 있는 '불변 컬렉션' 이다.- 예외를 던지는 메서드보다 유연하고 사용하기 쉽다.
null
을 반환하는 메서드보다 오류 가능성이 적다.
Optional
적용시켜보기
적용 전
public static <E extends Comparable<E>> E max(Collection<E> c) {
if(c.isEmpty()) {
throw new IllegalArgumentException("빈 컬렉션");
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return result;
}
- 컬렉션에서 가장 큰 값을 구하는 메서드이다.
c.isEmpty()
는null
이 들어왔는지 판단하게 되고null
이 들어온다면IllegalArgumentException
예외를 던진다.
적용 후
public static <E extends Comparable<E>> Optional<E> max2(Collection<E> c) {
if(c.isEmpty()) {
return Optional.empty();
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result) > 0) {
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}
- 컬렉션이 비었을 때,
Optional.empty()
를 반환한다. - 컬렉션이 비어있지 않다면,
Optional.of(result)
를 반환한다. Optional
을 사용한다면,null
때문에 예외를 던지거나null
을 반환할 일은 없다.
테스트
@Test
public void maxTest() {
List<Integer> integers = List.of(2, 4, 10, 22, 33, 11, 55, 25, 29);
Integer max = max(integers);
Optional<Integer> optional = max2(integers);
System.out.println(max);
optional.ifPresent(System.out::println);
}
실행결과
55
55
Optional
과 Stream
함께 이용해보기
public static <E extends Comparable<E>> Optional<E> max3(Collection<E> c) {
return c.stream().max(Comparator.naturalOrder());
}
@Test
public void maxTest2() {
List<Integer> integers = List.of(2, 4, 10, 22, 33, 11, 55, 25, 29);
Optional<Integer> optional = max3(integers);
optional.ifPresent(System.out::println);
}
- 코드가 매우 짧아졌지만, 기존의 동작을 잘 수행하고 있다.
Optional
의 핵심은 반환 값이 없을 수도 있음을 코드를 통해 컴파일 타임에 미리 알려주는 것이다. 어찌 보면 예외 처리를 강제하는 검사 예외와 취지가 비슷하다.
옵셔널 활용하기
1. 기본 값 정하기 (optional.orElse()
)
@Test
public void optionalDefaultValue() {
List<Integer> integers = new ArrayList<>();
Integer optional = max3(integers).orElse(0);
System.out.println("optional = " + optional);
}
orElse()
는Optional
내부 값이empty
라면 지정된 값을 반환한다.- 만일 내부 값이 존재한다면
get()
과 같은 결과를 갖는다.
- 만일 내부 값이 존재한다면
2. 기본 예외 정하기 (optional.orElseThrow()
)
@Test
public void optionalDefaultThrow() {
List<Integer> integers = new ArrayList<>();
Integer optional = max3(integers).orElseThrow(IllegalArgumentException::new);
System.out.println("optional = " + optional);
}
orElseThrow()
는Optional
내부 값이empty
라면 지정된 예외를 반환한다.- 만일 내부 값이 존재한다면
get()
과 같은 결과를 갖는다.
- 만일 내부 값이 존재한다면
3. 항상 값이 채워져있다고 가정한다. (optional.get()
)
@Test
public void optionalDefaultGet() {
List<Integer> integers = new ArrayList<>(List.of());
Integer optional = max3(integers).get();
System.out.println("optional = " + optional);
}
- 값이 무조건 채워져있다고 가정한다.
- 없다면,
NoSuchElementException
을 맞이하게 된다.
4. 초기 생성 비용이 아주 클 때 유용하다. (optional.orElseGet()
)
Connection connection = getConnection(dataSource).orElseGet(() -> getLocalConnection());
- 초기 생성 비용이 아주 큰데 값의 여부에 따라 생성할 수도 있고 생성하지 않을 수도 있는 경우
orElseGet()
을 사용한다.- 값이 처음 필요할 때
Supplier
를 이용해 생성하므로 초기 생성 비용을 낮춘다.
- 값이 처음 필요할 때
기타 Optional
메서드들
isPresent()
: 값이 있다면true
, 없다면false
이다.- 사실 이 메서드를 통해 작성할 수 있는 로직은
orElse()
로도 보통 가능하다.
- 사실 이 메서드를 통해 작성할 수 있는 로직은
orElse()
로 대체가 가능하기 때문에 단독으로는 사용하지 않는 것이 권장된다.
Stream
에서의 isPresent()
활용
streamOfOptionals
.filter(Optional::isPresent)
.map(Optional::get)
- 이렇게
filter()
에 적절히 활용하는 식으로 사용하는 것은 좋다.
자바 9에서는
Optional
에stream()
이라는 메서드가 추가되었다.Optional
을Stream
으로 변환해주는 어댑터의 역할을 한다.
Optional.ofNullable(person).map(Person::getAddress)
.map(Address::getPostalCode)
.orElse(null);
반환 값을 Optional
로 사용하면 안 되는 경우
컬렉션, 스트림, 배열 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸지 말자
- 컨테이너의 경우에 이를테면
List
라면, 단순히 빈List
를 반환하는 것이 좋을 수 있다.
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면, Optional<T>
를 반환하자
- 이외의 경우엔 오히려 성능 낭비만 될 수 있다.
성능이 너무 중요한 상황에서 옵셔널을 사용하면, 객체 생성비용조차 부담이 될 수 있다.
- 박싱된 기본타입을 담은 옵셔널은 사용하지 말자.
- 대신
OptionalInt
와 같은 전용 클래스를 사용하자. 조금이나마 성능을 좋게 만들 수 있다.
- 대신
- 옵셔널을 컬렉션의 키, 값 혹은 배열의 원소로 사용하지 말자.
- 쓸데없이 복잡하고 혼란과 오류의 가능성을 키운다.
핵심 정리
- 반환 값이 없을 수도 있을 때
Optional
을 활용해보자.- 다만, 성능 저하를 생각하여
null
이나 예외처리도 고려하자.
- 다만, 성능 저하를 생각하여
- 반환 값 이외의 용도는 굳이 쓸 이유가 없으니 반환 값에만 이용하자.
null
을 반환하는 것은 최대한 꺼리자.
레퍼런스
반응형
'Java > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바, 쉽게 정리하기 - item 57. 지역변수의 범위를 최소화하라 (0) | 2023.06.21 |
---|---|
이펙티브 자바, 쉽게 정리하기 - item 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2023.06.21 |
이펙티브 자바, 쉽게 정리하기 - item 54. null 이 아닌, 빈 컬렉션이나 빈 배열을 반환하라 (0) | 2023.06.10 |
이펙티브 자바, 쉽게 정리하기 - item 53. 가변 인수는 신중히 사용하라 (0) | 2023.06.09 |
이펙티브 자바, 쉽게 정리하기 - item 52. 다중정의는 신중히 사용하라 (0) | 2023.06.09 |