Java

[Java] Optional

Tommy__Kim 2024. 1. 26. 16:44

들어가기 앞서 

자바를 사용해 프로그래밍을 할 경우 종종 마주치는 예외는 NullPointerException일 것입니다.

public class Person{
	private Car car;
	public Car getCar(){
		return car;
    }
}

public class Car {
	private Insurance insurance;
	public Insurance getInsurance{
		return insurance;
	}
}

public class Insurance{
	private String name;
	public String getName(){
		return name;
	}
}

public String getCarInsuranceName(Person person){
	return person.getCar().getInsurance().getName();	// NullPointerException
}

 

getCarInsuranceName 메서드에서 person은 car의 주소값에 대해  null을 가지고 있습니다. null인 참조값에 대해 특정 행동을 하려고 하기 때문에 NullPointerException이 발생하면서 프로그램 실행이 중단될 것입니다. 

 

이러한 문제를 방지하기 위해 보수적으로 null확인 코드를 추가해 NullPointerException 발생을 줄일 수 있습니다. 

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 체크를 한 후 사용하기 때문에 보다 NullPointerException에 안전한 코드라고 할 수 있습니다. 

그러나 null을 체크하는 코드가 비즈니스 로직보다 많기 때문에 코드의 가독성이 떨어집니다. 

 

Null로 인해 발생하는 문제

  1. 에러의 근원
    • NullPointerException은 자바에서 가장 흔히 발생하는 에러
  2. 코드를 어지럽힌다
    • 때로 중첩된 null 확인 코드를 추가해야 하므로 가독성이 떨어진다.
  3. 아무 의미가 없다
    • null은 아무 의미도 표현하지 않음
  4. 자바 철학에 위배된다
    • 자바는 개발자로부터 모든 포인터를 숨겼지만 Null 포인터의 경우 그렇지 못함
  5. 형식 시스템에 구멍을 만든다
    • null이 할당되기 시작하면서 다른 부분으로 null이 퍼질 경우 애초에 null이 어떤 의미로 사용되었는지 알 수 없음

Optional

Optional은 선택형 값을 캡슐화 하는 클래스입니다. 

Optional에는 값이 있을 수도 있고, 없을 수도 있습니다.

값이 있을 경우 Car 인스턴스를 사용할 수 있고, 값이 없을 경우 Optional.empty()를 반환합니다. 

null을 참조할 경우 NullPointerException이 발생하지만 Optional의 경우 Optional 인스턴스를 사용하므로 다양하게 활용을 할 수 있습니다.

Optional 메소드

Optional은 다양한 메소드를 지원합니다. 

자주 사용하는 메소드에 대해서 간단하게 알아보고자 합니다. 

Optional 생성 

Optional.of()

Optional<Car> optionalCar = Optional.of(car);	// car은 null이어서는 안된다.

Optional.of()의 경우 Optional에 담으려고 하는 값이 확실하게 존재하는 경우 사용합니다.

만약 담으려고 하는 값이 null일 경우 NullPointerException이 발생합니다.

 

Optional.ofNullable()

Optional<Car> optionalCar = Optional.ofNullable(car);	// car은 있을수도 있고 없을수도 있다.

Optional.ofNullable()의 경우 Optional에 담으려고 하는 값이 null일 수도 있을 경우 사용합니다. 

 

Optional.empty()

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

Optional.empty()를 사용하면 Optional 안에 아무것도 들어있지 않은 Optional을 생성할 수 있습니다.

 

Optional 언랩 

Optional.get()

Optional<Car> optionalCar = Person.getCar();
Car car = optionalCar.get();

래핑 된 값이 있으면 해당 값을 반환하고 값이 없으면 NoSuchElementException을 발생시킵니다.

 

Optional.orElse(T other)

Car defaultCar = new Car();
Optional<Car> optionalCar = Person.getCar();
Car car = optionalCar.orElse(defaultCar);

Optional이 값을 가지고 있지 않을 때 기본값을 제공할 수 있는 메소드 입니다. 

 

Optional.orElseGet(Supplier<? extends T> other)

Optional<Car> optionalCar = Person.getCar();
Car car = optionalCar.orElseGet(() -> new Car());

orElseGet() 메소드의 경우 orElse() 메소드의 게으른 버전입니다. 

orElse의 경우 Optional안에 값이 존재하던 존재하지 않던 무조건 실행됩니다. 반면 orElseGet()의 경우 값이 존재하지 않을 때에만 코드가 실행됩니다.

 

Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)

Optional<Car> optionalCar = Person.getCar();
Car car = optionalCar.orElseThrow(() -> new IllegalStateException("null car"));

orElseThrow() 메소드의 경우 Optional 안에 값이 비어있을 때 예외를 발생시킬 수 있습니다.

 

Optional.ifPresent(Consumer<? super T> consumer)

Optional<Car> optionalCar = Person.getCar();
optionalCar.ifPresent(System.out::println);

ifPresent() 메소드의 경우 Optional 안에 값이 존재할 경우 동작합니다. 

 

Optional.ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

Optional<Car> optionalCar = Person.getCar();
optionalCar.ifPresentOrElse(System.out::println, () -> System.out.println("value is null");

ifPresentOrElse() 메소드의 경우 값이 존재할 경우 실행할 Consumer와, 값이 없을 경우 실행할 수 있는 Runnable을 인수로 받는다. 

 

그 외 

Optional.filter(Predicate<? super T> predicate)

Optional<Car> optionalCar = Person.getCar();
Car car = optionalCar.filter(c -> c.getInsurance().getName().startsWith("Dongbu");

filter 메소드를 사용하면 Optional 안에 있는 값을 특정 조건에 맞춰서 거를 수 있습니다.

 

Optional.map(Function<? super T. ?> mapper)

Optional<Car> optionalCar = Person.getCar();
String name = optionalCar.map(c -> c.getInsurance.getName());

map 메소드를 사용하면 Optional 안에 있는 값을 변환할 수 있습니다. 

 

Optional.flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

Optional<Optional<Car>> optionalCar = Person.getCar();
String name = optionalCar.flatMap(c -> c.getInsurance.getName());

Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리합니다.