티스토리 뷰
728x90
반응형
옵셔널 반환은 신중히 하라
- 자바 8이전에는 메서드에서 반환할 수 있는 값이 없는 경우 두 가지를 취할 수 있었습니다. 하지만 각각의 단점이 존재합니다.
- 1. 예외를 던집니다.
- 단점 1 - 예외는 반드시 예외적인 상황에서만 사용해야 합니다.
- 단점 2 - 예외는 실행 스택을 추적을 캡처하기 때문에 비용이 비쌉니다.
- 2. 반환 타입이 객체인 경우 null 반환
- 단점 1 - 클라이언트 코드에서는 항상 null 체크 로직을 추가해야하며 그렇지 않은 경우 NPE가 발생합니다.
💡 Optional의 등장
- Optional이란, 값이 있을 수도 있고 없을 수도 있는 객체입니다.
- Optional은 원소 하나를 가지는 불변 컬렉션입니다.
- 자바 8이전의 코드보다 null-safe한 로직을 처리할 수 있게끔 해줍니다.
- Optional을 반환하여 조금 더 유연한 로직을 작성할 수 있게끔 해줍니다.
💡 Optional의 주요 메서드
📜 Optional.empty()
- 내부 값이 비어있는 Optional 객체를 반환합니다.
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
}
📜 Optional.of(T value)
- 내부 값이 value인 Optional 객체를 반환합니다.
- 만약 value가 null인 경우 NPE가 발생합니다.
public final class Optional<T> {
private Optional(T value) {
this.value = Objects.requireNonNull(value); // null인 경우 NPE 발생
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
}
📜 Optional.ofNullable(T value)
- value가 null이면 empty Optional 객체를 반환하고, null이 아닌 경우 Optional.of 메서드로 객체 생성 후 반환합니다.
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
}
📜 T get()
- Optional 내의 값을 반환합니다.
- 만약 Optional 내부 값이 null인 경우 NoSuchElementException이 발생합니다.
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
📜 boolean isPresent()
- Optional 내부의 값이 있으면 true, null인 경우 false를 반환합니다.
public boolean isPresent() {
return value != null;
}
📜 boolean isEmpty()
- Optional 내부의 값이 null이라면 true, null이 아닌 경우 false를 반환합니다.
public boolean isEmpty() {
return value == null;
}
📜 Optional.filter
- Optional에 filter 조건을 걸어 조건에 맞을 때만 Optional 내부 값을 사용합니다.
- 조건이 맞지 않으면 Optional.empty를 반환합니다.
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
📜 Optional.map
- Optional 내부의 값을 Function을 통해 가공합니다.
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
💡 null 반환과 Optional<T> 반환 비교
- 컬렉션에서 최댓값을 구합니다.(컬렉션이 비어있으면 예외를 던집니다.)
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;
}
- Optional<E>를 반환하도록 변경하였습니다. 그리고 컬렉션이 비어있다면 빈 Optional 객체를 생성 후 반환합니다.
- Optional을 반환하는 메서드에서는 절대 null을 반환해서는 안됩니다.
public static <E extends Comparable<E>> Optional<E> max(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 활용 방법
1. 기본값을 정해둘 수 있습니다.
public static void main(String[] args) {
Optional<String> word = Optional.ofNullable(null);
String s = word.orElse("단어 없음");
System.out.println(s);
}
2. 원하는 예외를 던질 수 있습니다.
- 이렇게 하면 예외가 실제로 발생하지 않는한 예외 생성 비용은 들지 않습니다.
public static void main(String[] args) {
Optional<String> word = Optional.ofNullable(null);
String s = word.orElseThrow(NullPointerException::new);
System.out.println(s);
}
3. 항상 값이 있다고 가정할 수 있습니다.
- Optional에 항상 값이 채워져 있다고 확신한다면 곧바로 값을 꺼내 사용하는 선택지도 있습니다. 다만 잘못된 판단이라면 예외가 발생하게 됩니다.
public static void main(String[] args) {
Optional<String> word = Optional.ofNullable("HELLO");
String s = word.get();
System.out.println(s);
}
4. 기본값을 설정하는 비용이 큰 경우
- 기본값을 설정하는 비용이 아주 커서 부담되는 경우 orElseGet을 사용하면, 값이 처음 필요할 때 Supplier를 사용해 생성하므로 초기 생성 비용을 낮출 수 있습니다.
public static void main(String[] args) {
Connection connection = getConnection(datasource).orElseGet(() -> getLocalConnection());
}
💡 반환타입을 Optional<T>를 사용하면 안되는 경우
- 반환값으로 옵셔널을 사용한다고해서 무조건 득이 되는것은 아닙니다. 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안됩니다. 빈 Optional<List<T>>를 반환하기 보다는 List<T>를 반환하는게 좋습니다.
- 빈 List를 반환하는게 좋은 이유는 Optional로 반환하게 된다면 클라이언트에서 Optional 처리 코드를 넣어야 합니다. 아이템 54 >>
- 박싱된 기본 타입을 담은 Optional은 기본 타입 자체보다 무거울 수밖에 없습니다. 값을 두 겹이나 감싸기 때문입니다. 그래서 자바 API 설계자들은 int, long, double 전용 Optional 클래스를 준비해놨습니다. OptionalInt, OptionalLong, OptionalDouble입니다. 이 Optional들도 Optional<T>가 제공하는 메서드를 거의 다 제공합니다. 이렇게 대체제까지 있으니 박싱된 기본 타입을 담은
Optional을 반환하는 일은 없도록 해야합니다. 다만 Boolean, Byte, Char 등은 예외일 수 있습니다.
💡 반환타입을 Optional<T>로 해야하는 경우
- 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환합니다. 하지만 이렇게 하더라도 Optional<T>를 반환하는 데는 댓가가 따릅니다. Optional도 엄연히 새로 할당하고 초기화해야하는 객체이고, 그 안에서 값을 꺼내려면 메서드를 호츨해야 하니, 한 단계를 더 거치는 셈입니다. 그래서 성능이 중요한 상황에서는 적절히 고려해봐야 합니다.
✔️ 정리
- Optional을 사용하여 반환하는 경우 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 반환하기보다는 컨테이너 자체를 반환하는게 좋습니다.
- 박싱된 기본 타입(int, long, double)을 담은 옵셔널을 반환하지 않도록 합시다. 다만 Boolean, Byte, Char등은 예외입니다.
참고자료)
https://escapefromcoding.tistory.com/241
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item58. 전통적인 for문보다는 for-each문을 사용하라 (0) | 2022.08.09 |
---|---|
이펙티브 자바 - Item57. 지역변수의 범위를 최소화하라 (0) | 2022.08.08 |
이펙티브 자바 - Item54. null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.08.06 |
이펙티브 자바 - Item53. 가변인수는 신중히 사용하라 (0) | 2022.08.06 |
이펙티브 자바 - Item52. 다중정의는 신중하게 사용하라 (0) | 2022.08.05 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- java userThread와 DaemonThread
- transactional outbox pattern
- spring boot redisson destributed lock
- redis 대기열 구현
- 레이어드 아키텍처란
- 자바 백엔드 개발자 추천 도서
- redis sorted set으로 대기열 구현
- java ThreadLocal
- polling publisher spring boot
- 트랜잭셔널 아웃박스 패턴 스프링부트
- spring boot redisson sorted set
- 공간 기반 아키텍처
- pipe and filter architecture
- @ControllerAdvice
- spring boot 엑셀 다운로드
- spring boot excel download oom
- microkernel architecture
- space based architecture
- redis sorted set
- transactional outbox pattern spring boot
- JDK Dynamic Proxy와 CGLIB의 차이
- spring boot excel download paging
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot redis 대기열 구현
- service based architecture
- 서비스 기반 아키텍처
- pipeline architecture
- 람다 표현식
- spring boot poi excel download
- spring boot redisson 분산락 구현
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
글 보관함