[교재 EffectiveJava] 아이템 55. 옵셔널 반환은 신중히 하라

[교재 EffectiveJava] 아이템 55. 옵셔널 반환은 신중히 하라

728x90

빈 값 처리방법 Optioanl

자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을때 취할 수 있는 선택지는 두가지였다.

1) 예외를 던진다.

2) null 을 반환한다. (반환 타입이 객체 참조일 경우)

두 가지 방법 모두 허점이 있다. 예외는 진짜 예외적인 상황에서 사용해야한다. null을 반환하면 클라이언트에서 별도의 null 처리 코드를 추가해야한다.

자바 8 후에 한가지 방법이 생겼다.

3) Optional 로 처리한다.

3)번의 방법은 null 이 아닌 T 타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다. 아무것도 담지 않은 옵셔널은 '비었다'라고 말한다. Optional은 원소를 최대 1개 가질 수 있는 '불변' 컬렉션이다. Optional 가 Collection를 구현하지는 않았지만, 원칙적으로는 그렇다.

Optional을 반환하는 메서드는 예외를 던지는 메서드보다 유연하고 사용하기 쉬우며, null을 반환하는 메서드보다 오류 가능성이 작다.

Optional 포스팅 바로가기

https://devfunny.tistory.com/330

예제로 이해하기

1) 예외를 던진다.

package com.java.effective.item55; import java.util.Collection; import java.util.Objects; import java.util.Optional; public class Main { public static > E max(Collection 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; } }

2) Optional 을 반환한다.

package com.java.effective.item55; import java.util.Collection; import java.util.Objects; import java.util.Optional; public class Main { public static > Optional maxOptional(Collection c) { /* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */ if (c.isEmpty()) { return Optional.empty(); } E result = null; for (E e : c) { if (result == null || e.compareTo(result) > 0) { result = Objects.requireNonNull(e); } } /* 값이 든 Optional 을 반환한다. */ return Optional.of(result); /* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자. null 을 허용하는 Optional 을 반환하자. */ // return Optional.ofNullable(result); } }

3) 스트림의 종단 연산 중 상당수가 옵셔널을 반환한다. 스트림 버전을 보자.

package com.java.effective.item55; import java.util.Collection; import java.util.Comparator; import java.util.Objects; import java.util.Optional; public class Main { public static > Optional maxOptionalStream(Collection c){ return c.stream().max(Comparator.naturalOrder()); } }

Optional 반환 선택의 기준

옵셔널은 검사 예외와 취지가 비슷하다. 반환값이 없을 수도 있음을 클라이언트에게 명확히 알려준다. 비검사 예외를 던지거나 null을 반환한다면 API 사용자가 그 사실을 인지하지 못해서 예상치못한 에러가 발생할 수도 있다. 하지만 검사 예외를 던지면 클라이언트에서 반드시 이에 대처하는 코드를 작성해넣어야 한다.

클라이언트는 Optional 을 반환받고, 그 값의 여부에 따라 취할 행동을 선택해야한다.

1) 기본값 설정

package com.java.effective.item55; import java.util.*; public class Main { public static void main(String[] args) { List words = new ArrayList<>(); words.add("A"); words.add("B"); words.add("C"); String lastWordInLexicon = maxOptional(words).orElse("단어 없음"); } public static > Optional maxOptional(Collection c) { /* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */ if (c.isEmpty()) { return Optional.empty(); } E result = null; for (E e : c) { if (result == null || e.compareTo(result) > 0) { result = Objects.requireNonNull(e); } } /* 값이 든 Optional 을 반환한다. */ return Optional.of(result); /* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자. null 을 허용하는 Optional 을 반환하자. */ // return Optional.ofNullable(result); } }

2) 원하는 예외 던지기

package com.java.effective.item55; import java.util.*; public class Main { public static void main(String[] args) { List words = new ArrayList<>(); words.add("A"); words.add("B"); words.add("C"); String lastWordInLexicon = maxOptional(words).orElseThrow(RuntimeException::new); } public static > Optional maxOptional(Collection c) { /* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */ if (c.isEmpty()) { return Optional.empty(); } E result = null; for (E e : c) { if (result == null || e.compareTo(result) > 0) { result = Objects.requireNonNull(e); } } /* 값이 든 Optional 을 반환한다. */ return Optional.of(result); /* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자. null 을 허용하는 Optional 을 반환하자. */ // return Optional.ofNullable(result); } }

3) 값이 있다고 가정하고 꺼내기

만약 꺼냈는데 값이 존재하지 않다면, NoSuchElementException 이 발생한다.

package com.java.effective.item55; import java.util.*; public class Main { public static void main(String[] args) { List words = new ArrayList<>(); words.add("A"); words.add("B"); words.add("C"); String lastWordInLexicon = maxOptional(words).get(); } public static > Optional maxOptional(Collection c) { /* 빈 컬렉션일 경우 빈 Optional 을 반환한다. */ if (c.isEmpty()) { return Optional.empty(); } E result = null; for (E e : c) { if (result == null || e.compareTo(result) > 0) { result = Objects.requireNonNull(e); } } /* 값이 든 Optional 을 반환한다. */ return Optional.of(result); /* Optional 을 사용하는 경우에는 null을 절대 반환하지 말자. null 을 허용하는 Optional 을 반환하자. */ // return Optional.ofNullable(result); } }

4) Supplier를 인수로 받는 orElseGet을 사용하여 값 초기 설정 비용을 낮출 수 있다.

5) isPresent() 메서드 사용하기

Optional parentProcess = ph.parent(); System.out.println("부모 PID: " + (parentProcess.isPresent() ? String.valueOf(parentProcess.get().pid()) : "N/A")); /* 다듬은 코드 */ System.out.println("부모 PID: " + ph.parent().map(h -> String.valueOf(h.pid()).orElse("N/A"));

- isPresent() : Optional이 채워져있을 경우 true, 비어있을 경우 false 를 반환한다.

- 스트림을 사용한다면 옵셔널들을 Stream>로 받아서, 그 중 채워진 옵셔널들에서 값을 뽑아 Stream에 건네 담아 처리하는 경우도 있다.

streamOfOptionals .filter(Optional::isPresent) .map(Optional::get)

자바 9에서 Optional에 stream() 메서드가 추가되었다. 이는 Optional 을 Stream으로 변환해주는 어댑터다. 옵셔널에 값이 있으면 그 값을 원소로 담은 스트림으로, 값이 없다면 빈 스트림으로 변환한다. 이를 Stream의 flatmap 메서드와 조합하면 앞의 코드를 다음처럼 명료하게 바꿀 수 있다.

streamOfOptionals.flatMap(Optional::stream)

flatMap 관련 포스팅 바로가기

https://devfunny.tistory.com/458

Optional 선택 상황

컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다. 빈 Optional>를 반환하기보다는 빈 List를 반환하는게 좋다. 빈 컨테이너를 그대로 반환하면 클라이언트에서 옵셔널 처리 코드를 넣지 않아도 된다.

즉, Optional를 사용해야하는 상황은 아래와 같다.

1) 결과가 없을 수 있다.

2) 클라이언트가 이 상황을 특별하게 처리를 해야한다.

박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수 밖에 없다. 자바 API는 int, long, double 전용 옵셔널 클래스도 제공한다. OptionalInt, OptionalLong, OptionalDouble이다. 이 옵셔널들도 Optional가 제공하는 메서드를 거의 다 제공한다. 이렇게 대체재가 있으므로 박싱된 기본타입을 담은 옵셔널을 반환하면 안된다.

from http://devfunny.tistory.com/628 by ccl(A) rewrite - 2021-11-26 16:27:47