본문 바로가기

Java

Optional 정리

Optional의 등장배경

NullPointerException 은 Java 로 개발을 하면서 가장 많이 겪는 예외 중 하나일 것이다.

우리는 이 NullPointerException 을 피하기위해 다음과 같은 코드를 짜곤한다.

 

public String getCarName(Person person) {
	if (person != null) {
		Car car = person.getCar();
		if (car != null) {
			return car.getName();
		}
	}
}

 

혹은 다음과 같은 방법도 있다.

 

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

 

첫번째 방식을 깊은 의심, 두번째 방식을 너무 많은 출구 라고 표현하기도 한다.

하지만 위 두 방식은 코드를 어지럽힌다. 때로는 null 체크를 놓칠수도 있다.(에러가 발생된다)

이와 같은 상황에서 Optional 이 등장하였다.

 

Optional 소개

다음과 같은 Person 클래스가 있다고 하자.

public class Person {
	private Car car;

	public Car getCar{
		return car;
	}
}

만약 어떤 사람이 차를 소유하지 않고 있다면 Person 클래스의 car 변수는 null 이다.

 

그러나 car 변수의 자료형이 Car 가 아닌 Optional<Car> 라면

public class Person {
	private Optional<Car> car;

	...
}

차를 소유 하지 않고 있는 사람의 car 변수는 텅빈 Optional 객체를 반환한다.

 

 출처 :https://livebook.manning.com/book/java-8-in-action/chapter-10/41

 

null 참조는 NullPointerException을 발생시키지만 텅빈 Optional 객체는 그렇지 않다.

우리는 null 이 아닌 Optional 객체(비록 텅비었을 지라도) 여러가지 방법으로 활용할 수 있다.

그 방법을 차근히 알아보자.

 

 

Optional 만들기

1. 빈 Optional

empty() 메서드를 이용해 빈 Optional 객체를 얻을 수 있다.

Optional<Car> optCar = Optional.empty();

 

2. null 이 아닌 값으로 Optional 만들기

of 메서드를 이용하여 만들 수 있다.

Optional<Car> optCar = Optional.of(car);

 

3. null 일 수 있는 값으로 Optional 만들기

만약 Car 가 null 일 수 있는 상황에서는 ofNullable 메서드를 이용하자

Optional<Car> optCar = Optional.ofNullable(car);

 

Optional 사용하기

값 추출 및 변환, map

optional 의 사용법은 스트림과 매우 유사하다

optional 은 map 메서드를 지원하는데 만약 Optional 이 값을 포함하고 있다면 map 의 인수로 제공된 함수가 값을 바꾸고, Optional 이 비어있다면 아무일도 하지 않는다.

Optional<String> name = optCar.map(Car::getName);

이 외에도 filter 등 스트림에서 사용하는 연산을 Optional 에서도 사용가능하다.

 

값 얻기, orElse

그렇다면 Optional 에 저장되어 있는 값은 어떻게 얻을 수 있을가?

orElse() 메서드를 활용할 수 있다.

만약 빈 Optional 일 경우에는 orElse 의 인자로 주어진 값을 리턴한다.

public String getName(Optional<Car> car){
	return car.map(Car::getName)
    		.orElse("Unknown");
}

 

orElse(T other) 보다는 orElseGet(Supplier<? extends T> other)

orElse 는 유용한 메서드이지만 orElseGet 을 사용하기를 추천한다.

orElse 는 Optional 값의 유무에 상관없이 전달된 함수가 실행되지만 orElseGet 은 Optional 에 값이 비어있는 경우에만 인자로 전달된 함수가 호출된다. 한마디로 orElse 의 게으른 버전이라고 할 수 있다.

 

public class Car {
	private String name;

	public Car(final String name) {
		System.out.println(name);
		this.name = name;
	}
}

Car 객체가 생성될 때 이름을 출력한다면 다음과 같은 결과를 얻을 수 있다.

public class Main {
	public static void main(String[] args) {
		Car car = new Car("car");
		Optional<Car> optCar = Optional.ofNullable(car);
		optCar.orElse(new Car("orElse"));
		optCar.orElseGet(() -> new Car("orElseGet"));
		
		//출력 결과
//		car 
//		orElse
	}
}

 

값이 비었을 경우 예외처리, orElseThrow

만약 값을 얻으려고하고 값이 없는경우 예외를 발생시키려 한다면 orElseThrow 메서드를 사용할 수 있다.

filter 와 함께 사용한다면 다음과 같이 사용할 수 있겠다.

Car car = new Car("k3");
Optional<Car> optCar = Optional.ofNullable(car);

optCar.filter(c -> "k5".equals(c.getName()))
	.orElseThrow(() -> new IllegalArgumentException("k5 차량이 아닙니다."));

optCar에는 이름이 k3인 Car 객체가 들어있고, filter 를 거쳐 optCar 는 빈 객체가 된다.

이때 orElseThrow 는 optCar 가 비어 있기 때문에 인자로 전달된 예외를 발생 시킨다.

 

 

ifPresent, isPresent

ifPresent(Consumer<? super T> consumer) 는 값이 존재할 경우 인수로 전달된 동작을 실행하고 값이 비어있을 경우 아무일도 일어나지 않는다.

isPresent 메서드는 값이 있는지 없는지 boolean 값을 리턴한다.

반응형