Optional
업데이트:
null
- 에러의 근원이다 : NullPointerException 에러
- 코드를 어지럽힌다 : null 체크로 인해 가독성이 떨어진다.
- 아무 의미가 없다 : null은 아무 의미도 포함하지 않는다.
- 자바 철학에 위배 된다 : 개발자로부터 모든 포인터를 숨겼다. 하자만 예외가 null포인터다
- 형식 시스템에 구멍을 만든다 : null은 무형식으로 모든 참조 형식에 할당할 수 있다. 이런 식으로 할당이 되면 어떤 의미로 사용되었는지 알 수 없다.
Optional 클래스
선택형값을 캡슐화 하는 클래스다. 어떤 객체의 값이 없는 경우 null을 할당하는게 아닌 빈 Optional을 할당하고 값이 있는경우 Optional 클래스는 값을 감싼다.
값이 있거나 없을 경우 Optional클래스를 사용함으로써 모델의 의미가 더 명확해진다.
Optional 적용 패턴
빈 Optional
정적 팩토리 메서드 Optional.empty로 빈 Optional객체를 얻을 수 있다.
Optional<car> optCar = Optional.empty();
null이 아닌 값으로 Optional 만들기
Optional.of로 null이 아닌 값을 포함하는 Optional을 만든다.
Car가 null일경우 즉시 NullPointerException이 발생
(Optional을 사용하지 않았을 경우에는 Car에 접근할때 발생됨)
Optional<car> optCar = Optional.of(car)
null이 값으로 Optional 만들기
Optional.ofNullable로 null 값을 저장하는 Optional을 만든다.
Car가 null이면 빈 Optional을 반환한다.
Optional<car> optCar = Optional.ofNullable(car)
맵으로 Optional
null인지 확인하기 위해 map을 제공한다.
Optional이 비어있는경우 아무일도 일어나지 않는다
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optINsurance.map(Insurance::getName);
flatMap으로 Optional 객체 연결
flatMap : 함수를 인수로 받아서 다른 스트림을 반환하는 메서드로 인수로 받은 함수를 적용해서 생성된 각각의 스트림에서 콘텐츠만 남긴다.
flatMap을 활용하면 이차원 Optional을 일차원 Optional로 변환이 가능하다.
public String getCarInsuranceName(Optional<Person> person){
return person.flatMap(Persion::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown") // Optional이 빈 경우 기본값 사용
}
도메인 모델에서 Optional 적용 방법
도메인 모델에서는 Optional을 사용했을때 데이터를 직렬화 할 수 없다. Optional클래스는 선택형 반환값을 지정하는 것으로 못박았고 그래서 필드 형식으로 사용한다는걸 가정하지 않아 Serializable인터페이스를 구현하지 않는다.
Optional을 사용하려면 Optional로 값을 반환받을 수 있는 메서드를 추가하는 방식이 권장된다.
public class Persion{
private Car car;
public Optional<car> getCarAdOptional(){
return Optional.ofNullable(car);
}
}
Optional 스트림 조작(자바 9)
자바 9에서는 Optional에 stream() 메서드를 추가했다. Optional 스트림을 값을 가진 스트림으로 변환할떄 활용 가능하다.
public Set<String> getCarInsuranceNames(List<Person> persons){
return persons.stream()
.map(Person::getCar) // Optional<car> 스트림으로 변환
.map(optCar -> optCar.flatMap(Car::getInsurance)) // flatMap 을 통해 Optional<Insurance>로 변환
.map(optIns -> optIns.map(Insurance::getName)) // Optional<String>으로 매핑
.flatMap(Optonal::stream) // Stream<Optional<String>>을 현재 이름을 포함하는 Stream<String>으로 변환
.collect(toSet()); // 결과 문자열을 집합으로 수집
}
마지막 결과를 얻으려면 빈 Optional을 제거하고 값을 언랩해야 한다. 아래 코드처럼 filter, map을 순차적으로 사용하여 구현할 수 있다.
Stream<Optional<String>> stream = ...
Set<String> stream.filter(Optional::isPresent)
.map(Optional::get)
.collect(toSet());
디폴트 액션과 Optional 언랩
- get() : 래핑된 값이 있으면 해당 값을 반환하고 없으면 NoSuchElementException 발생함, 중첩된 null 체크와 크게 다르지 않다.
- orElse() : Optional에 값이 없을때 기본값을 제공할 수 있다.
- orElseGet(Supplier<? extends T> other) : orElse의 게으른 버전, 빈 값일 경우에만 Supplier이 실행된다.
- orElseThrow(Supplier<? extends T> exceptionSupplier) : 빈 값일경우 예외 발생, 예외를 선택할 수 있다.
- ifPresent(Consumer<? super T> consumer) : 값이 있을때문 인수로 넘겨준 동작이 실행한다.
- ifPresentElse(Consumer<? super T> action, Runnable emptyAction) : 빈값일때 실행할 수 있는 Runnable을 인수로 받는다는 점만 제외하고 ifPresent와 같다
두 Optional 합치기
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> persion, Optional<Car> car){
ewruen pweaon.flatMap(p -> car.map(c - findCheapestInsurance(p,c)));
}
필러로 특정값 거르기
filter메서드는 프레디케이트를 인수로 받아 Optional 객체의 값이 있는 경우 프리디케이트와 일치하면 그값을 반환 한다.
Optional<Insurance> optInsurance = ..;
optInsurance.filter(insurance -> "test".equals(insurance.getrName())
.ifPresent(x -> System.out.println("ok")));
Optional 예제
null 대상 Optional 감싸기
//null 체크 필요
Object value = map.get("key");
//Optional 사용
Optional<Object> value = Optional.ofNullable(map.get("key"));
예외와 Optional
Optional을 감싸 예외를 처리 할 수 있다.
public static Optional<Integer> stringToInt(String s){
try {
return Optional.of(Integer.parseInt(s));
} catch(NumberFormafException e){
return Optional.empty();
}
}
기본형 Optional 사용 안함
Optional의 최대 요소는 한개이므로 기본형 특화 클래스로 성능을 개선 할 수 없고 일반 Optional과 혼용할 수 없다.
댓글남기기