11.1 값이 없는 상황을 어떻게 처리할까
- 클래스 내부에 필드로 객체를 가지는 상황에서 객체가 비어 있고, 해당 필드에 접근하려 하면 NullPointerException이 발생한다.
- 보수적인 자세로 if (객체 == null)을 많이 사용하여 null을 피해 갈 수도 있을 것이다.
- 변수를 접근할 때마다 if가 늘어나고 코드 들여 쓰기가 늘어난다.
- 이와 같은 반복 패턴 코드를 ‘깊은 의심’이라고 한다.
- null 때문에 발생하는 문제
- 에러의 근원이다: NullPointerException은 가장 흔한 에러
- 코드를 어지럽힌다: null 확인 코드를 추가해야 하므로 아무 의미가 없다: null은 아무 의미도 표현하지 않는다.
- 자바 철학에 위배된다: 자바는 개발자로부터 모든 포인터를 숨겼지만 null은 예외
- 형식 시스템에 구멍을 만든다: null은 무형식이며 정보를 포함하고 있지 않으므로 모든 참조 형식에 할당할 수 있다. 때문에 시스템에 null이 퍼졌을 때 어떤 의미인지 파악하기 쉽지 않다.
11.2 Optional 클래스 소개
- Optional은 선택형값을 캡슐화하는 클래스다.
- 변수에 값이 없으면 null을 할당하는 것이 아니라 Optional<T>로 설정할 수 있다.
- 값이 있으면 Optional은 값을 감싸고, 없으면 Optional.empty를 반환한다.
- Optional.Empty()는 Optional의 특별한 싱글턴 인스턴스를 반환하는 정적 팩토리 메서드다.
- Optional을 사용하면서 필드 변수가 필수적으로 있어야 하는 것인지, 없을 수도 있는 필드인지 모델의 의미가 명확해짐을 확인할 수 있다.
- 그렇다고 모든 null 참조를 Optional로 대치하는 것은 바람직하지 않다.
- Optional의 역할은 더 이해하기 쉬운 API를 설계하도록 돕는 것이다.
- 메서드의 시그니처만 보고도 선택형값인지 여부를 구별할 수 있다.
11.3 Optional 적용 패턴
빈 Optional
Optional<Car> optCar = Optional.empty();
null이 아닌 값으로 만들기
car가 null이었다면 NullPointerException 발생
Optional<Car> optCar = Optional.of(car);
null값으로 만들기
car가 null이어도 예외 발생하지 않고 Optional.Empty 반환
Optional<Car> optCar = Optional.ofNullable(car);
11.4 맵으로 Optional의 값을 추출하고 변환하기
- Optional의 map은 스트림의 map과 개념적으로 비슷하다. Optional 객체를 최대 요소 개수가 1개 이하인 데이터 컬렉션으로 생각할 수 있다.
- Optional이 값을 포함하면 map의 인수로 제공된 함수가 값을 바꾼다.
- 비어있으면 아무 일도 일어나지 않는다.
Car car = new Car("does");
Optional<Car> optCar = Optional.of(car);
Optional<String> name = optCar.map(Car::getName);
- 위의 예에서 getName()의 반환 타입이 단순한 값일 때는 (String 반환) map을 사용하면 되지만 반환 타입이 Optional<String>이라면 Optioinal<Optional<String>>이 되어 버린다.
- 이런 상황에서는 flatMap을 사용하면 1차원 Optional로 만들 수 있다.
도메인 모델에 Optional을 사용했을 때 데이터를 직렬화할 수 없는 이유
- Optional의 용도는 선택형 반환값을 지원하는 것
- 애초에 필드 형식으로 사용할 것을 가정하지 않았으므로 Serializable 인터페이스를 구현하지 않는다.
- 따라서 도메인 모델에 Optional을 사용하면 직렬화 모델을 사용하는 도구나 프레임 워크에서 문제가 생길 수 있다.
- 따라서 getter 등의 반환 값에서만 사용할 것을 권장한다.
Optional 스트림 조작
Optional의 stream() 메서드는 각 Optional이 비어 있는지 확인하고 값이 있으면 그 값을 스트림으로 감싸서 반환한다. 즉 Stream<Optionial<String>>이었던 타입에 flatMap(Optional::stream)을 적용하면 null이 아닌 값들만 모아서 스트림으로 만들어 준다. 즉 filter(Optional::ifPresent).map(Optional::get)과 같은 일을 해준다.
디폴트 액션과 Optional 언랩
- get()은 가장 간단하면서 가장 안전하지 않은 메서드. 값이 없으면 NoSuchElementException이 발생한다.
- orElse()는 값이 없을 때 기본 값을 제공한다. 하지만 기본 값이 객체 생성일 경우 값이 있든 없든 매번 생성한다.
- orElseGet()은 orElse의 게으른 버전이다. 값이 없을 때만 인자로 받은 Supplier가 실행되기 때문이다.
- orElseThrow()는 get과 비슷하지만 예외를 선택할 수 있다.
- ifPresent()는 값이 존재할 때 인자로 받은 Consumer를 실행한다.
- ifPresentOrElse()는 Optional이 비어있을 때 실행할 수 있는 Runnable을 인수로 받는다는 점만 ifPresent()와 다르다.
'개발 서적 > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] Chapter13. 디폴트 메서드 (0) | 2022.04.05 |
---|---|
[모던 자바 인 액션] Chapter7. 병렬 데이터 처리와 성능 (0) | 2022.03.02 |
[모던 자바 인 액션] Chapter4. 스트림 소개 (0) | 2022.02.28 |