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

+ Recent posts