이번 주제 키워드
- null 참조의 문제점과 null을 멀리해야 하는 이유
- null 대신 Optional
- Optional 활용
값이 없는 상황을 어떻게 처리할까?
- Null 참조를 반환하는 방식으로 소유하고 있지 않음을 표현하는 경우가 있다.
- 이러한 경우는 런타임 NPE가 발생하면서 프로그램 실행이 중단된다.
- 이러한 NPE를 피하려면 null 확인 코드를 추가해서 예외 문제를 해결하려고 할 것이고 이것은 다양한 문제를 일으킨다!!
null 때문에 발생하는 문제
- 에러의 근원이다
- 코드를 어지럽힌다.
- 아무 의미가 없다.
- null은 아무 의미도 표현하지 않으므로 값이 없음을 표현하는 방법으로는 적절하지 않다.
- 자바 철학에 위배된다.
- 자바는 모든 포인터를 숨겼지만 null 포인터는 예외
- 형식 시스템에 구멍을 만든다.
Optional 클래스
- 자바8은 하스켈가 스칼라의 영향을 받아서 java.util.Optional라는 새로운 클래스 제공한다.
- Optional은 선택형 값을 캡슐화하는 클래스이다.
- 이는
값이 없을 수 있음
을 명시적으로 보여주는 것이다.
- Optional 클래스를 사용하면서 모델의 의미가 더 명확해졌다
- Optional을 최대 1개의 원소를 가지고 있는 특별한 Stream이라고 생각하시면 좋다.
- Optional 클래스와 Stream 클래스 간에 직접적인 구현이나 상속관계는 없지만 사용 방법이나 기본 사상이 매우 유사하기 때문
출처
Optional 객체 만들기
빈 Optional
- 정적 팩토리 메서드 Optional.empty로 빈 Optional 객체를 얻을 수 있다.
Optional<Car> optCar = Optional.empty();
private static final Optional<?> EMPTY = new Optional<>();
null이 아닌 값으로 Optional 만들기
- 정적 팩토리 메서드 Optional.of로 null이 아닌 값을 포함하는 Optional을 만들 수 있다.
- 인수로 넣는 값이 null이라면 즉시 NPE가 발생한다.
Optional<Car> optCar = Optional.of(car);
null 값으로 Optional 만들기
- 마찬가지로 정적 팩토리 메서드 Optional.ofNullable로 null 값을 저장할 수 있는 Optional을 만들 수 있다.
- 인수로 넣는 값이 null이면 빈 Optional 객체가 반환된다.
Optional<Car> optCar = Optional.ofNullable(car);
map으로 Optional의 값을 추출하고 변환하기
- 보통 객체의 정보를 추출할 때는 Optional을 사용할 때가 많다.
String name = null;
if(insurance != null){
name = insurance.getName();
}
- 예를 들어 보험회사의 이름을 추출한다고 가정하자. 다음 코드처럼 이름 정보에 접근하기 전에 insurance가 null인지 확인해야 한다.
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
- 이런 유형의 패턴에 사용할 수 있도록 Optional은 map 메서드를 지원한다.
- Optional의 map은 스트림의 map 메서드와 개념적으로 비슷하다.
- Optional이 값을 포함하면 map의 인수로 제공된 함수가 값을 바꾼다.
- 비어 있으면 아무 일도 일어나지 않는다.
flatMap으로 Optional 객체 연결
public String getCarInsuranceName(Person person) {
return person.getCar().getInsurance().getName();
}
Optional<Person> optPerson = Optional.of(Person);
Optional<String> name = optPerson.map(Person::getCar).map(Car::getInsurance).map(Insurance::getName);
- 위의 코드는 컴파일 되지 않는다.
optPerson.map(Person::getCar)
- getCar은 Optional를 반환
- 따라서 위의 코드의 반환값은Optional<Optional> 타입이라 getInsurance 메서드를 지원하지 않기 때문이다.
- 스트림처럼 flatMap 활용, 이차원 Optional을 일차원 Optional로 평준화
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown");
}
도메인 모델에 Optional을 사용했을 때 데이터를 직렬화할 수 없는 이유
- Optional 클래스는 필드 형식으로 사용할 것을 가정하지 않았으므로 Serializable 인터페이스를 구현하지 않았다.
- 따라서 도메인 모델에 Optional을 사용한다면 직렬화 모델을 사용하는 도구나 프레임워크에서 문제가 생길 수 있다.
- 만약 직렬화 모델이 필요하다면 변수는 일반 객체로 두되, Optional로 값을 반환받을 수 있는 메서드를 추가하는 방식이 권장된다.
public class Person {
private Car car;
public Optional<Car> getCarAsOptional() {
return Optional.ofNullable(car);
}
}
Optional 스트림 조작
- 자바 9에서는 Optional을 포함하는 스트림을 쉽게 처리할 수 있도록 Optional에 stream()메서드를 추가했다.
- Stream에서 가장 유용한 함수 체인의 형태는 아래와 같다.
Stream<Optional<String>> stream = ...;
Set<String> result = stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(toSet());
디폴트 액션과 Optional 언랩
get
- 값을 읽는 가장 간단한 메서드면서 동시에 가장 안전하지 않은 메서드이다.
- 값이 없으면 NoSuchElementException을 발생시키므로 값이 반드시 있다고 가정할 수 있는 상황이 아니면 get 메서드를 사용하지 말자
orElse
- Optional이 값을 포함하지 않을 때 기본값을 제공할 수 있다.
orElseGet(Supplier<? extends T> other)
- orElse 메서드에 대응하는 게으른 버전의 메서드이다. Optional에 값이 없을 때만 Supplier가 실행된다.
- 기본값이 필요한 상황에서 사용
orElseThrow(Supplier<? extends X> exceptionSupplier)
- Optional이 비어있을 때 예외를 발생시킬 수 있으며, 발생시킬 예외의 종류를 정할 수 있다.
ifPresent(Consumer<? super T> consumer)
- 값이 존재할 때 인수로 넘겨준 동작을 실행할 수 있다.
- 값이 없으면 아무일도 일어나지 않는다.
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
- 자바 9
- Optional이 비었을 때 실행할 Runnable을 인수로 받는다
두 Optional 합치기
- Optional에서
map
과 flatMap
은 Optional이 비어있다면 empty Optional
을 반환한다.
- 두 Optional에 대한 연산을 map과 flatMap을 적절히 활용하여 수행할 수 있다.
필터로 특정값 거르기
- filter 메서드는 프레디케이트를 인수로 받는다.
- Optional 객체가 값을 가지며 프레디케이트와 일치하면 filter 메서드는 그 값을 반환
- 그렇지 않으면 빈 Optional 객체 반환
Optional을 사용한 실용 예제
잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기
Object value = map.get("key");
Optional<Object> value = Optional.ofNullable(map.get("key"));
- 참조하는 객체에 대하여 null이 될 수 있는 경우가 있다면 Optional 객체로 대체한다.
예외와 Optional 클래스
- 자바 API에서 값을 제공할 수 없을 때 null을 반환하는 대신 예외를 발생시킬 때가 있다.
- 해당 메서드가 Optional을 반환하도록 모델링할 수 있다.
public static Optional<Integer> stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s));
} catch(NumberFormatException e) {
return Optional.empty();
}
}
기본형 Optional을 사용하지 말아야 하는 이유
- Optional과 함께 기본형 특화 클래스인 OptionalInt, OptionalLong, OptionalDouble이 존재한다.
- 하지만 Optional의 최대 요소 수는 한 개이므로 성능개선이 되지 않는다.
- 또한 map, flatMap, filter 등을 지원하지 않으르로 다른 일반 Optional과 혼용할 수 없으므로 기본현 Optional을 사용하지 않는것을 권장한다.
추가로 생각해볼 부분