티스토리 뷰
반응형
자바8 이전
자바8 이전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때 두가지 선택지가 있었다.
- 예외 던지기
- 예외는 진짜 예외적인 상황에서만 사용해야함
- 예외를 생성할 때 스택 추적 전체를 캡쳐하므로 비용이 큼
- 반환 타입이 객체 참조라면 null 반환
- 클라이언트에서 null을 반환하는 메서드를 호출할 때 추가적으로 null 처리 코드를 작성해야함
//컬렉션에서 최대값을 구함 - 컬렉션이 비었으면 예외 던지기
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("Empty collection");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
자바8 이후
- 자바8부터는 Optional가 생기면서 null이 아닌 T타입을 참조하거나 아무것도 담지 않을 수 있게됨.
- 옵셔널은 원소를 최대 1개 가질 수 있는 불변 컬렉션
- 보통은 T를 반환하지만, 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional를 반환 → 유효한 반환값이 없을 때는 빈 결과를 반환
- 옵셔널을 반환하는 메서드가 예외를 던지거나 null을 반환하는 것보다 오류 가능성이 적음
- 옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.
//컬렉션에서 최댓값 구하기 - Optional<E> 반환
//정적 팩터리를 사용해 옵셔널을 생성
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<E> 반환)
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
return c.stream().max(Comparator.naturalOrder());
}
Optional을 반환했을 때 장점
옵셔널은 검사 예외와 취지가 비슷함 → 반환 값이 없을 수도 있음을 API 사용자에게 명확히 알려줌. 검사 예외를 던진다면 클라이언트는 이에 대한 처리 코드를 작성할 것임
//옵셔널 활용 1 - 기본 값 정해두기
String lastWordInLexicon = max(words).orElse("단어 없음...");
//옵셔널 활용 2 - 원하는 예외 던지기
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
//옵셔널 활용 3 - 항상 값이 채워져있다고 가정
//잘못 판단한 것이라면 NoSuchElementException 발생
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
//옵셔널 활용 4 - 기본값 설정 비용이 클 경우
//Supplier<T>를 인수로 받는 orElseGet
public static String orElseGetBenchmark() {
return Optional.of("fruit").orElseGet(() -> getRandomName());
}
//옵셔널 활용 5 - filter, map, flatMap, ifPresent
//옵셔널 활용 6 - isPresent 메서드
//옵셔널이 채워져있으면 true, 비어있으면 false 반환
//isPresent를 사용한 코드는 대게 앞에 언급한 메서드들로 대체 가능 -> 그게 더 간결하고 명확함
public class ParentPid {
public static void main(String[] args) {
ProcessHandle ph = ProcessHandle.current();
// isPresent 메서드를 사용한 코드
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("Parent PID: " + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
// 위 코드를 map 메서드로 대체 다듬은 코드 -> 더 간결,명확
System.out.println("Parent PID: " +
ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
}
}
마지막 옵셔널 활용 6에서의 코드를 스트림을 사용할 경우에 다음과 같다.
//옵셔널에 값이 있다면 꺼내서 매핑
streamOfOptionals
.filter(Optional::isPresent)
.map(Optional::get);
//자바9부터는 Optional을 Stream으로 변환해주는 Optional.stream() 추가
//옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 없으면 빈 스트림으로 변환
//flatMap 메서드와 조합하여 더 명료한 코드로 수정 가능
streamOfOptionals.flatMap(Optional::stream)
옵셔널을 사용하면 안되는 경우
- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안됨. 빈 Optional<List>를 반환하는 것보다 빈 List를 반환하는게 좋음
- int,long,double 전용 옵셔널 클래스(OptionalInt, OptionalLong, OptionalDouble) 가 있기 때문에 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.
- 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는게 적절한 상황은 거의 없음 (복잡성을 키워 오류 가능성을 키움)
옵셔널을 사용하면 좋은 경우
결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야할 때 Optional를 반환
단, 옵셔널을 사용하면 새로운 객체를 할당하고 값을 꺼내기 위해 메서드를 호출하는 등으로 성능이 저하될 수 있다. → 성능이 중요할땐 안맞을 수 있음
결론
값을 반환하지 못할 가능성이 있고, 호출할 때마다 반환 값이 없을 가능성을 염두에 둬야하는 메서드라면 옵셔널을 반환해야할 수 있다.
옵셔널 반환에는 성능 저하가 뒤따르므로 상황에 맞춰 사용하자.
옵셔널을 반환값 이외의 용도로 쓰는 경우는 매우 드물다.
반응형
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] 57.지역변수의 범위를 최소화하라 (0) | 2022.04.06 |
---|---|
[Effective Java] 56.공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2022.04.06 |
[Effective Java] 54.null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2022.04.04 |
[Effective Java] 53.가변인수는 신중히 사용하라 (0) | 2022.04.04 |
[Effective Java] 52.다중정의는 신중히 사용하라 (0) | 2022.04.04 |
댓글