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과 혼용할 수 없다.

댓글남기기