10. null 대신 Optional
1965년 토이호어가 알골을 설계하면서 처음 null 레퍼런스가 등장.
그는 "구현하기 쉬웠기 때문에 null을 도입했다"라고 당시를 회상.
"컴파일러의 자동 확인 기능으로 모든 레퍼런스를 안정하게 사용할 수 있을 것"을 목표로 정함.
당시 null 레퍼런스 및 예외로 값이 없는 상황을 가장 단순하게 구현 할 수 있다고 판단.
결과적으로 null 및 관련 예외 탄생.
여러 해가 지난 후 호어는 당시 null 및 예외를 만든 결정을 가리켜 "억만 달러짜리 실수"라고 표현.
자바를 포험해 최근 수십 년간 탄생한 대부분의 언어 설계에는 null 레퍼런스 개념을 포함.
예전 언어와 호환성을 유지하려는 목적도 있겠지만 호어의 말 처럼 "구현하기 쉬웠기 때문에" null 레퍼런스 개념을 포함했을 것.
1. 값이 없는 상황 처리?
1.1 보수적인 자세로 NullPointerException 줄이기
- NullPointerException을 피하려면?
> 대부분의 프로그래머는 필요한 곳에 다양한 null 확인 코드를 추가해 null 예외문제를 해결.
> 모든 변수가 null인지 의심할 수 있으므로 변수를 접근할 때 마다 if가 추가되면서 코드 양 증가 우려.
* 이와 같은 반복 패턴 코드를 "깊은 의심"이라 부름.
1.2 null 때문에 발생하는 문제
- 에러의 근원
> NullPointerException은 자바에서 가장 흔히 발생하는 에러.
- 코드를 어지럽힘.
> 때로는 중첩된 if 확인 코드를 추가헤야 하므로 null 때문에 코드 가독성 저하.
- 아무 의미가 없음.
> null은 아무 의미도 포함하지 않음. 특히 정적 형식 언어에서 값이 없음을 표현하는 방법으로는 부적절.
- 자바 철학에 위배.
> 자바는 개발자로부터 모든 포인터를 숨김. 하지만 예외가 있으며 그 것이 바로 null 포인터임.
- 형식 시스템에 구멍을 만듦.
> null은 무형식이며 정보를 포함하고 있지 않으므로 모든 레퍼런스 형식에 null을 할당할 수 있음.
> 이런 식으로 null이 할당되기 시작하면서 시스템의 다른 부분에 null이 퍼졌을 때 애초에 null이 어떤 의미로 사용되었는지 알 수 없음.
1.3 다른 언어는 null 대신 무엇을 사용?
- 최근 그루비 같은 언어에서는 안전 내비게이션 연산자(?.)를 도입해 null 문제 해결.
- 하스켈, 스칼라 등의 함수형 언어는 아예 다른 관점에서 null 문제에 접근.
- 하스켈은 선택형 값을 저장할 수 있는 MayBe라는 형식 제공.
> MayBe는 주어진 형식의 값을 갖거나 아니면 아무 값도 갖지 않을 수 있음.
* 따라서 null 레퍼런스 개념이 자연스럽게 사라짐.
- 스칼라도 T 형식의 값을 갖거나 아무 값도 갖지 않을 수 있는 Option[T]라는 구조 제공.
> Option형식에서 제공하는 연산을 사용해서 값이 있는지 여부를 명시적으로 확인.
* 즉, null 확인
> 형식 시스템에서 이를 강제하므로 null과 관련한 문제가 일어날 가능성이 줄어듦.
- 자바8은 "선택형값" 개념의 영향을 받아 java.util.Optional<T>라는 새로운 클래스 제공.
2. Optional 클래스 소개
- 자바8은 하스켈과 스칼라의 영향을 받아 java.util.Optional<T>라는 새로운 클래스 제공.
- Optional은 선택형값을 캡슐화 하는 클래스.
- 값이 있으면 Optional 클래스는 값을 감싸며 값이 없으면 Optional.empty 메서드로 Optional을 반환.
> Optional.empty는 Optional의 특별한 싱글턴 인스턴스 반환 정적 팩토리 메서드.
- null 레퍼런스와 Optional.empty()의 차이점
> 의미상 비슷하지만 차이점 발생
> null을 참조하려면 NullPointerException이 발생. Optional.empty()는 Optional객체이므로 다양한 방식으로 활용 가능.
- Optional 클래스를 사용하면서 모델의 의미가 더 명확해짐.
- Optional을 이용하면 값이 없는 상황이 우리 데이터에 문제가 있는 것인지 아니면 알고리즘의 버그인지 명확하게 구분 가능.
- 모든 null 레퍼런스를 Optional로 대치하는 것은 바람직하지 않음.
- Optional은 더 이해하기 쉬운 API를 설계하도록 돕는 것.
3. Optional 적용 패턴
3.1 Optional 객체 생성.
빈 Optional
- 정적 팩토리 메서드 Optional.empty()로 빈 Optional 객체 생성 가능.
null이 아닌 값으로 Optional 만들기
- 정적 팩토리 메서드 Optional.of로 null이 아닌 값을 포함하는 Optional 생성 가능.
> 이 경우 NullPointerException이 발생할 수 있음.
null값으로 Optional 만들기
- 정적 팩토리 메서드 Optional.ofNullable로 null값을 저장할 수 있는 Optional 생성 가능.
- get 메서드를 이용해 Optional 값 획득 가능.
> Optional이 비어있으면 get을 호출했을 때 예외 발생.
* 즉, Optional을 잘못 하용하면 결국 null을 사용했을 때와 같은 문제 발생.
3.2 맵으로 Optional의 값을 추출하고 변환.
- Optional은 map 메서드 지원.
- Optional의 map 메서드는 스트림의 map 메서드와 개념적으로 비슷.
- 스트림의 map은 스트림의 각 요소에 제공된 함수를 적용하는 연산.
> 여기서 Optional객체를 최대 요소의 개수가 한 개 이하인 데이터 컬렉션으로 생각 가능.
- Optional이 값을 포함하면 map의 인수로 제공된 함수가 값을 바꿈.
> Optional이 비어있으면 아무 일도 일어나지 않음.
3.3 flatMap으로 Optional 객체 연결
- faltmap 메서드를 통해 이차원 Optional이 일차원 Optional로 변환.
도메인 모델에 Optional을 사용했을 때 데이터를 직렬화할 수 없는 이유
- Optional로 도메인 모델에서 값이 꼭 있어야 하는지 아니면 값이 없을 수 있는지 여부를 구체적으로 표현 가능.
- Optional 클래스의 설계자는 이와는 다른 용도로만 Optional 클래스를 사용할 것을 가정.
- 브라이언 고츠(자바 언어 아키텍트)는 Optional의 용도가 선택형 반환값을 지원하는 것이라고 명확하게 명시.
- Optional 클래스는 필드 형식으로 사용할 것을 가정하지 않았으므로 Serializable 인터페이스를 구현하지 않음.
> 따라서 도메인 모델에서 Optional을 사용한다면 직렬화 모델을 사용하는 도구나 프레임워크에 문제 발생 가능.
- 이런 문제에도 불구하고 여전히 Optional을 사용해서 도메인 모델을 구성하는 것이 바람직하다고 판단됨.
> 특히, 객체 그래프에서 일부 또는 전체가 null일 수 있는 상황이라면 더욱 그렇다고 판단됨.
- 직렬화 모델이 필요할 경우 Optional로 값을 반환받을 수 있는 메서드를 추가하는 방식 권장.
3.4 디폴트 액션과 Optional 언랩
- Optional이 비어있을 때 디폴트값을 제공할 수 있는 orElse 메서드로 값 획득 가능.
- Optional 클래스는 Optional 인스턴스에서 읽을 수 있는 다양한 인스턴스 메서드 제공.
> get()은 값을 읽는 가장 단순한 메서드면서 동시에 가장 안전하지 않은 메서드.
* 래핑된 값이 있으면 해당 값을 반환, 없으면 NoSuchElementException 발생.
* Optional 값이 반드시 있다고 가정할 수 있는 상황이 아니면 get 메서드 사용은 바람직 하지 않음.
> orElse(T other) 메서드를 이용하면 Optional이 값을 포함하지 않을 때 디폴트값 제공 가능.
> orElseGet(Supplier<? extends T)는 orElse 메서드에 대응하는 게으른 버전의 메서드.
* Optional에 값이 없을 때만 Supplier가 실행되기 떄문.
* 디폴트 메서드를 만드는 데 시간이 걸리거나 Optional이 비어있을 때만 디폴트값을 생성하고 싶다면 orElseGet(Supplier<? extends T) 사용.
> orElseThrow(Supplier<? extends X> exceptionSupplier)는 Optional이 비어있을 때 예외를 발생시킨다는 점에서 get과 비슷.
* 하지만 이 메서드는 발생시킬 예외의 종류 선택 가능.
> ifPresent(Consumer<? super T> consumer)를 이용하면 값이 존재할 때 인수로 넘겨준 동작을 실행 가능. 값이 없으면 아무일도 일어나지 않음.
3.5 필터로 특정값 거르기
- Optional 객체가 값을 가지며 프레디케이드와 일치하면 filter 메서드는 그 값을 반환하고 그렇지 않으면 빈 Optional 객체를 반환.
- Optional은 최대 한 개의 요소를 포함할 수 있는 스트림과 같다고 설명. 이 사실을 적용하면 filter연산의 결과를 쉽게 이해 가능.
- Optional이 비어있다면 filter연산은 아무동작도 하지 않음.
표 10-1 Optional 클래스의 메서드
메서드 |
설명 |
empty |
빈 Optional 인스턴스 반환. |
filter |
값이 존재하며 프레디케이트와 일치하면 값을 포함하는 Optional을 반환하고, 값이 없거나 프레디케이트와 일치하지 않으면 빈 Optional 반환. |
flatMap |
값이 존재하면 인수로 제공된 함수를 적용한 결과 Optional을 반환하고, 값이 없으면 빈 Optional을 반환. |
get |
값이 존재하면 Optional이 감싸고 있는 값을 반환하고, 값이 없으며 NoSuchElementException이 발생. |
ifPresent |
값이 존재하면 지정된 Consumer를 실행하고, 값이 없으면 아무 일도 일어나지 않음. |
isPresent |
값이 존재하면 true를 반환하고, 값이 없으면 false를 반환. |
map |
값이 존재하면 제공된 매핑 함수를 적용. |
of |
값이 존재하면 값을 감싸는 Optional을 반환하고, 값이 null 이면 NullPointException을 발생. |
ofNullable |
값이 존재하면 값을 감싸는 Optional을 반환하고, 값이 null 이면 빈 Optional을 반환. |
ofElse |
값이 존재하면 값을 반환하고, 값이 없으면 디폴트값을 반환. |
ofElseGet |
값이 존재하면 값을 반환하고, 값이 없으면 Supplier에서 제공하는 값을 반환. |
ofElseThrow |
값이 존재하면 값을 반환하고, 값이 없으면 Supplier에서 생성한 예외를 발생. |
4. Optional을 사용한 실용 예제
- 자바8에서 새롭게 제공하는 Optional 클래스를 효과적으로 사용하려면 값이 없는 상황을 처리하던 기존의 알고리즘과는 다른 관점에서 접근해야 함.
> 즉, 코드 구현만 바꾸는 것이 아니라 네이티브 자바 API와 상호작용하는 방식도 바꿔야 함.
4.1 잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기
- null을 반환하는 것보다는 Optional을 반환하는 것이 더 바람직.
- Map의 get 메서드 시그니처는 수정불가, 단 get 메서드의 반환값은 Optional로 감쌀 수 있음.
- if-then-else를 추가하거나 Optional.ofNullable을 이용하는 두가지 방법 존재.
4.2 예외와 Optional
- 자바 API는 어떤 이유에서 값을 제공할 수 없을 때 null을 반환하는 대신 예외를 발생시킬 때도 존재.
- 전형적인 예로 문자열을 정수로 변환하는 Integer.parseIng(String) 정적 메서드임.
> 문자열을 정수로 바꾸지 못할 때 NumberFormatException 발생.
> 즉, 문자열이 숫자가 아니라는 사실을 예외로 알리는 것.
- 정수를 변환할 수 없느 문자열 문제를 빈 Optional로 해결 가능.
> 즉, parseInt가 Optional을 반환하도록 모델링 가능.
기본형 Optional과 이를 사용하지 말아야 하는 이유
- 스트림처럼 Optional도 기본형으로 특화된 OptionalInt, OptionalLong, OptionalDouble등의 클래스 제공.
- Optional의 최대 요소 수는 한 개이므로 Optional에서 기본형 특화 클래스로 성능 개선 불가.
- 기본형 특화 Optional은 map, flatMap, filter등의 메서드를 지원하지 않으므로 사용할 것을 권장하지 않음.
5. 요약
- 역사적으로 프로그래밍 언어에서는 null 레퍼런스로 값이 없는 상황을 표현해옴.
- 자바8에서는 값이 있거나 없음을 표현할 수 있는 클래스 java.util.Optional<T>를 제공.
- 팩토러 메서드 Optional.empty, Optional.of, Optional.ofNullable 등을 이용하여 Optional 객체 생성 가능.
- Optional 클래스는 스트림과 비슷한 연산 수행. map, flatMap, filter등의 메서드 제공.
- Optional로 값이 없는 상황을 적절하게 처리하도록 강제 가능.
> 즉, Optional로 계상치 못한 null 예외 방지 가능.
- Optional을 활용하면 더 좋은 API 설계가 가능.
> 즉, 사용자는 메서드의 시그니처만 보고도 Optional값이 사용되거나 반환되는지 예측 가능.
'Java8 > Java 8 in Action' 카테고리의 다른 글
새로운 날짜와 시간 (0) | 2017.08.07 |
---|---|
디폴트 메서드 (0) | 2017.07.05 |
리팩토링, 테스팅, 디버깅 (0) | 2017.07.03 |
병렬 데이터 처리와 성능 (0) | 2017.06.29 |
스트림으로 데이터 수집 (0) | 2017.06.29 |