[모던 자바 인 액션] Optional

이주오·2021년 9월 13일
0

도서

목록 보기
10/15

이번 주제 키워드

  • null 참조의 문제점과 null을 멀리해야 하는 이유
  • null 대신 Optional
  • Optional 활용

값이 없는 상황을 어떻게 처리할까?

  • Null 참조를 반환하는 방식으로 소유하고 있지 않음을 표현하는 경우가 있다.
  • 이러한 경우는 런타임 NPE가 발생하면서 프로그램 실행이 중단된다.
  • 이러한 NPE를 피하려면 null 확인 코드를 추가해서 예외 문제를 해결하려고 할 것이고 이것은 다양한 문제를 일으킨다!!

null 때문에 발생하는 문제

  • 에러의 근원이다
  • 코드를 어지럽힌다.
  • 아무 의미가 없다.
    • 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에서 mapflatMap은 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을 사용하지 않는것을 권장한다.

추가로 생각해볼 부분

profile
동료들이 같이 일하고 싶어하는 백엔드 개발자가 되고자 합니다!

0개의 댓글