[모던 자바 인 액션] Optional 클래스
NULL로 인해 발생하는 문제
1. 에러의 근원
2. 코드를 어지럽힌다.
3. 아무 의미가 없다.
4. 자바 철학에 위배된다.
5. 형식 시스템에 구멍을 만든다.
다른 언어에서는 NULL대신에 무엇을 사용할까?
안전 내비게이션 연산자(그루비): 호출 체인에 NULL참조가 있으면 결과로 NULL반환
선택형값(스칼라, 하스켈 등): 주어진 형식의 값을 갖거나 아무값도 갖지 않을 수 있는 값
자바는 선택형값 개념의 영향을 받아서 Optional이라는 새로운 클래스를 제공
Optional 클래스란?
Optional이란 선택형값을 캡슐화하는 클래스이다. 값이 있으면 값을 감싸고, 없으면 optinoal.empty()를 반환한다. Optional이란 값을 사용함으로써 해당 값에 Null이 생길 수 있다는 것을 전달할 수 있다. 그렇다고 모든 값에 Optional을 사용하는 것이 좋은 것은 아니고, 시나리오 상 null값이 발생할 수 없는 곳에는 Optional을 사용하지 않고, 에러가 발생하는 경우에는 문제를 해결하려고 하는 것이 좋다.
Optional 클래스의 사용
# Optional 객체의 생성
# 빈 optional 객체 생성
Optional<Car> optCar = Optional.empty();
# null이 아닌 값으로 optional 객체 생성
Optional<Car> optCar = Optinal.of(car);
# null을 저장할 수 있는 Optinal
Optional<Car> optCar = Optional.ofNullable(car);
# map과 flatMap
# map -> Optional 내부의 값에 주어진 함수를 적용하여 변환
Optional<String> name = optInsurance.map(Insurance::getName);
# flatMap -> 2차원 Optional을 평면화
String name = person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
# stream
# 스트림 Stream<Optional<String>>을 Stream<String>으로 변환
persons.stream()
.map(Person::getCar)
.map(optCar -> optCar.flatMap(Car::getInsurance))
.map(optInsurance -> optInsurance.map(Insurance::getName))
.flatMap(Optional::stream)
.collect(Collectors.toSet());
stream을 사용하면 Optioanl<T> 안에 값이 들었는지 안들었는지 고려하지 않고 편하게 Stream<Optional<T>>를 Stream<T>로 변환할 수 있다.
# Optional 언랩
# get -> npe가 발생할 수 있어서 위험
Car car = optCar.get();
# orElse -> optional값이 빈 경우 반환할 기본값 설정
Car car = optCar.orElse(defaultCar);
# orElseGet -> optional값이 빈 경우 실행할 Supplier설정
Car car = optCar.orElseGet(() -> getDefaultCar());
# orElseThrow -> optinal값이 빈 경우 에러 throw
Car car = optCar.orElseThrow(() -> new Exception());
# ifPresent -> optional에 값이 존재하는지 판단
Boolean result = optCar.isPresent();
# ifPresentOrElse
optCar.isPresentOrElse((v) -> System.out.println(v), () -> System.out.println("default"));
Optional 클래스와 직렬화
Optinal클래스를 사용하면 해당 값이 꼭 있어야 하는지 없어도 되는 값인지 여부를 표현할 수 있다는 장점이 있다. 하지만, Optinal은 애초에 설계될 때 그러한 사용을 고려하고 만들어진 것이 아니기 때문에 직렬화를 사용하는 도구나 프레임워크에서 문제가 발생할 수 있다. 따라서 그런 경우에는 Optional로 값을 반환받을 수 있는 메서드를 추가하여 해결할 수 있다.
Optional의 실용 예제
1. null이 될 수 있는 곳을 Optional로 감싸기
2. Optional값이 비어 있을 때 의도한 예외 던지기
3. 기본형 Optional 사용 X -> 기본형 Optional은 map, stream과 같은 메서드를 제공하지 않고, 값도 1개 뿐이라 성능의 차이가 거의 없음