모던 자바 인 액션 11장 : null 대신 Optional 클래스

Adam·2024년 7월 4일
0

모던 자바 인 액션

목록 보기
11/20
post-thumbnail

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

보수적인 자세

  • 필요한 곳에 null 확인 코드를 추가해 null 예외 문제를 해결
  • 가독성이 감소하고 유지보수가 어려워 진다

null 때문에 발생하는 문제

  • 에러의 근원: NullPointerException은 자바에서 가장 흔히 발생하는 에러
  • 코드를 어지럽힌다: null 확인 코드로 코드의 가독성이 떨어진다
  • 아무 의미가 없다: null은 아무 의미도 표현하지 않는데, 정적 형식 언어에서 값이 없음을 표현하는 방법으로 적절하지 않음
  • 자바 철학에 위배: 자바는 포인터를 숨겼는데 이 예외 사항이 null 포인터다
  • 형식 시스템에 구멍을 만든다: 모든 참조 형식에 null을 할당하는 것이 가능, 이로 인해 null이 어떤 의미로 사용되었는지 알 기 힘들다

다른 언어에서 null 대신 무얼 사용하나?

  • 안전 내비게이션 연산자 (?): 호출체인에 null인 참조가 있을때는 null을 반환
  • 선택형값을 저장할 수 있는 Maybe 형식
  • T 형식의 값을 갖거나 아무 값도 갖지 않는 Option[T]

Optional 클래스 소개

값이 있으면 Optional 클래스는 값을 감싸고 없으면 Optional.empty 메서드로 Optional을 반환

Optional.empty()는 Optional 객체이므로 이를 참조해도 익셉션이 발생하지 않는다

public class Car{
	private Optional<Insurance> insurance;
	public Optional<Insurance> getInsurance(){
		return insurance;
	}
}
public class Insurance {
	private String name;
	public String getName() {
		return name;
	}
}

위 예시처럼 보험회사는 반드시 이름을 가져야 하기 때문에 String으로, 차는 보험을 갖고 있을 수 도 없을 수 도 있기 때문에 Optional로 표현하면 모델의 의미semantic이 더 명확해졌다

Optional 적용 패턴

Optional 객체 만들기

  1. 빈 Optional: Optional.empty로 빈 Optional 객체를 얻을 수 있다
Optional<Car> optCar = Optional.empty();
  1. null이 아닌 값으로 Optional 만들기: Optional.of으로 null이 아닌 값을 포함하는 Optional 생성, 이 경우 car이 null이면 NullPointerException이 발생
Optional<Car> optCar = Optional.of(car);
  1. null값으로 Optional 만들기: Optional.ofNullable로 null 값을 저장할 수 있는 Optional을 만들 수 있다. 이 경우 car이 null이면 빈 Optional 객체가 반환
Optional<Car> optCar = Optional.ofNullable(car);

get메서드로 Optional의 값을 가져올 수 있는데 이때 비어있으면 예외가 발생

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

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

스트림의 map은 스트림의 각 요소에 제공된 함수를 적용하는 연산

Optional이 비어있으면 아무 일도 일어나지 않는다

flatMap으로 Optional 객체 연결

스트림의 flatMap은 함수를 인수로 받아서 다른 스트림을 반환하는 메서드

함수를 적용해서 생성된 모든 스트림이 하나의 스트림으로 병합되어 평준화

public String getCarInsuranceName(Optional<Person> person) {
	return person.flatMap(Person::getCar)
								.flatMap(Car::getInsurance)
								.map(Insurance::getName)
								.orElse("Unknown");
}

Optional 스트림 조작

public Set<String> getCarInsuranceNames(List<Person> people) {
	return people.stream()
								.map(People::getCar)
								.map(optCar -> optCar.flatMap(Car::getInsurance))
								.map(optIns -> optIns.map(Insurance::getName))
								.flatMap(Optional::stream)
								.collect(toSet());}

Optional객체를 가져올때 null값일 수 있기 때문에 map 변환을 수행

디폴트 액션과 Optional 언랩

  • get(): 값을 읽는 가장 간단한 메서드지만 가장 안전하지 않은 메서드, 반드시 값이 있을 상황에만 사용
  • orElse: Optional이 값을 포함하지 않을 때 기본값을 제공할 수 있다
  • orElseGet: orElse의 게으른 버전의 메서드 Optional이 비어있을 때 기본값을 생성하고 싶을때 사용
  • orElseThrow: Optional이 비어 있을때 예외를 발생시키지만 예외의 종류를 선택 가능
  • ifPresent: 값이 존재할 때 인수로 넘겨준 동작을 실행, 값이 없으면 아무 일도 일어나지 않는다
  • ifPresentOrElse: Optional이 비었을 때 실행할 수 있는 Runnable을 인수로 받을 수 있다

두 Optional 객체를 함께 사용하는 것도 가능

필터로 특정값 거르기

optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())
	.ifPresent(x -> System.out.println("ok");

필터로 특정값이 있다면 그 값을 반환하고 없다면 Optional 객체를 반환한다

Optional을 사용한 실용 예제

  1. 잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기
Optional<Object>value = Optional.ofNullable(map.get("key"));

Map에서 단순 get 했을때 해당 값이 없으면 null을 반환하는데 Optional로 감싸서 Optional을 반환하게 바꿀 수 있다

  1. 예외의 Optional 클래스
public Optional<Integer> stringtoInt(String s) {
	try{
		return Optional.of(Integer.parseInt(s));
	} catch (NumberFormatException e) {
		return Optional.empty();
	}
}

위 예시에서 s가 null일 경우 동작하지 않지만 Optional을 사용해 null일때도 원하는 에러를 반환할 수 있게 된다

Optional 최대 요소 수는 한 개이므로 사용을 난발해서는 안된다

profile
Keep going하는 개발자

0개의 댓글