[도서][모던 자바 인 액션] null 대신 Optional 클래스

Junseo Kim·2021년 3월 10일
0

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

보수적인 자세로 NullPointerException 줄이기

null이 발생할 수 있는 곳에 null 확인코드를 추가해서 처리할 수 있다.

public String getCarInsuranceName(Person person) {
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance insurance = car.getInsurance();
            if (insurance != null) {
                return insurance.getName();
            }
        }
    }
    return "Unknown";
}

null 확인 코드 때문에 들여쓰기 수준이 증가하고 가독성도 좋지않다. 어떤 곳에서 null이 발생할지 확신하지 못한다면 모든 곳에 null 체크를 해줘야한다.

이런식으로 작성하여 들여쓰기 수준을 줄일 수 있지만 return을 하는 곳이 4군데나 되기 때문에 유지보수하기 힘들다.

public String getCarInsuranceName(Person person) {
    if (person == null) {
        return "Unknown";
    }
    Car car = person.getCar();
    if (car == null) {
        return "Unknown";
    }
    Insurance insurance = car.getInsurance();
    if (insurance == null) {
        return "Unknown";
    }         
    return insurance.getName();
}

null 때문에 발생하는 문제

null을 사용하면 어떤 문제가 발생할 수 있을까?

  • 에러의 근원: NullpointerException은 흔히 발생하는 에러다.
  • 코드를 어지럽힌다: null 체크를 해주다보면 가독성이 떨어진다.
  • 의미가 없다: null은 아무 의미도 표현하지 않는다.
  • 자바 철학에 위배: 자바는 개발자로부터 모든 포인터를 숨겼다. null 포인터만 빼고..
  • 형식 시스템에 구멍을 만든다: null은 무형식이고 정보를 포함하지도 않기 때문에 모든 참조 형식에 할당할 수 있다. 따라서 null이 어떤 의미로 사용되었는지 알 수가 없다.

Optional 클래스 소개

자바 8부터 null 대신 사용하기 위한 Optional< T >를 제공한다. Optional은 선택형 값을 캡슐화하는 클래스다. Optional은 값이 하나 있거나 없다. 값이 있다면 Optional 클래스는 값을 감싸며, 값이 없는 경우 Optional.empty 메서드로 Optional을 반환한다.

Optional.empty는 NullPointerException을 발생시키지 않는다. Optional을 사용하면 해당 타입의 반환 값은 Optional<타입>을 반환하게된다. 값이 없을 수도 있다는 것을 명시적으로 나타내는 것이다.

Optional을 이용하면 값이 없는 상황이 데이터에 문제가 있는 것인지 아니면 알고리즘의 버그인지 명확하게 구분할 수 있게된다. 모든 null 참조를 Optional로 대치하는 것은 바람직하지 않다.

Optional 적용 패턴

Optional 객체 만들기

  • 빈 Optional: Optional.empty();
  • null이 아닌 값으로 Optional 만들기: Optional.of();
  • null 값으로 Optional 만들기: Optional.ofNullable(); (null이라면 빈 Optional 객체 반환)

맵으로 Optional의 값을 추출하고 변환하기

Optional을 최대 요소 개수가 한 개 이하인 데이터 컬렉션으로 생각할 수 있다. Optional의 map연산은 스트림과 유사하다.

Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

flatMap으로 Optional 객체 연결

Optional의 map을 연속적으로 사용할 수 없다.

Optional<Person> optPerson = Optional.of(person);
Optional<String> name = 
    optPerson.map(Person::getCar) // Optional<Optional<Car>> 반환
    	     .map(Car::getInsurance) // 컴파일 불가
             .map(Insurance::getName);

이런 문제를 해결하기 위해 flatMap을 사용한다. 스트림의 flatMap처럼 이차원을 일차원으로 평준화시켜준다.

Optional<Person> optPerson = Optional.of(person);
Optional<String> name = 
    optPerson.flatMap(Person::getCar)
    	     .flatMap(Car::getInsurance)
             .map(Insurance::getName);

Optional 스트림 조작

자바 9부터 Optional에 stream() 메서드가 추가됐다. Optional 스트림을 값을 가진 스트림으로 변환할 때 이 기능을 유용하게 활용할 수 있다.

디폴트 액션과 Optional 언랩

  • get(): 간단하지만 안전하지 않다. 권장 x
  • orElse(): Optional 값이 없을 때 기본값 제공
  • orElseGet(Supplier): Optional에 값이 없을 때만 Supplier 실행
  • orElseThrow(Supplier): Optional이 비어있을 때 예외 발생
  • ifPresent(Consumer): 값이 존재할 때만 Consumer 실행
  • ifPresentOrElse(Consumer, Runnable): Optional이 비어있을 때 실행할 수 있는 Runnable을 받는다.

필터로 특정값 거르기

Optional의 filter 메서드는 Predicate을 인수로 받는다. Optional 객체가 값을 가지며 Predicate와 일치하면 filter 메서드는 그 값을 반환하고 그렇지 않으면 빈 Optional 객체를 반환한다. Optional이 비어있다면 아무 동작도 하지 않고, 값이 있다면 값에 Predicate을 적용한다. 만약 false가 결과라면 빈 상태가 된다.

Optional을 사용한 실용 예제

잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기

Optional<Object> value = Optional.ofNullable(map.get("key"));

예외와 Optional 클래스

public static Optional<Integer> stringToInt(String s) {
    try {
         return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

기본형 Optional을 사용하지 말아야 하는 이유

Optional도 기본형 특화 클래스가 존재하지만, Optional의 최대 요소 수는 한 개이므로 Optional에서는 기본형 특화 클래스로 성능을 개선할 수 없다. 기본형 특화 Optional은 map, flatMap, filter등을 지원하지 않는다.
반대 의견: 9. Optional 대신 OptionalInt, OptionalLong, OptionalDouble

참고
[Java] Optional

0개의 댓글