본문 바로가기
Learning-log/JAVA

[Java] Optional이란? Optional 활용법

by why제곱 2023. 8. 26.

1. NullPointerException

null처리가 취약한 코드

  • 예시
/* 주문 */
public class Order {
	private Long id;
	private Date date;
	private Member member;
	// getters & setters
}

/* 회원 */
public class Member {
	private Long id;
	private String name;
	private Address address;
	// getters & setters
}

/* 주소 */
public class Address {
	private String street;
	private String city;
	private String zipcode;
	// getters & setters
}
/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
	return order.getMember().getAddress().getCity();
}
  • 위 코드가 NPE에 취약한 이유
    • order 파라미터에 null 값이 넘어오면 order.getMember() , order.getMember().getAddress(), order.getMember().getAddress().getCity() 의 함수 호출부에서 null 처리를 하지 않으면 NPE 발생 가능성 농후

기존 NPE 방지 방식

  • 중첩 null 체크
if( order != null ) {
//코드
	if(member !=null ) {
		//코드
			if(address != null) { 
				//코드 
			}
	}
}
  • return 잔뜩 !
if(order == null) return;
//코드
if(member == null) return;
//코드
if(address == null) return;
//코드
  • null의 고질적인 문제
    • NPE 예외 발생 가능
    • NPE 방지를 위한 null 체크 로직 때문에 코드 가독성과 유지보수성 저하

Optional의 등장

<aside> 💡 함수형 언어들은 존재할지 안 할지 모르는 값을 표현할 수 있는 별개의 타입을 가짐 ⇒ Java8은 이런 함수형 언어의 접근방식을 적용하여, java.util.Optional<T>클래스 도입

</aside>

Optional이란?

존재할 수도 있지만 안할 수도 있는 객체. 즉, null이 될 수 있는 객체를 감싸고 있는 래퍼 클래스

  • 원소가 최대 하나밖에 없는 Collection 이나 Stream 으로 생각해도 됨
  • 효과
    • NPE 유발 가능한 null을 직접 다루지 않아도 됨
    • 수고롭게 null 체크를 하지 않아도 됨
    • 명시적으로 해당 변수가 null일 수 있다는 가능성을 표현 가능

Optional의 사용법

  1. 변수 선언하기
    • 제네릭 제공 ⇒ 변수 선언시 명시한 타입 파라미터에 따라 감쌀 수 있는 객체가 결정됨
    • 변수명에 “maybe” 또는 “opt”와 같은 접두사를 붙여 Optional 타입의 변수라는 것을 좀 더 명확히 나타내기도 함
  2. 객체 생성하기
    • Optiona.empty()
      • null을 담고 있는 빈 Optional 객체 생성
      • Optional을 내부적으로 미리 생성해놓는 싱클턴 인스턴스
    • Optional.of(value)
      • null이 아닌 객체를 담고 있는 Optional 객체 생성
      • null이 넘어올 경우 NPE 발생하므로 주의해서 사용할 것
    • Optional.ofNullable(value)
      • null인지 아닌지 확인할 수 없는 객체를 담고있는 Optional 객체 생성
      • 위 두 메서드를 합처놓은 메서드
  3. 객체 접근하기
    • get()
      • 비어있는 객체면 NoSuchElementException 던짐
    • orElse(T other)
      • 비어있는 Optional 객체에 대해 넘어온 인자 반환
    • orElseGet(Supplier<? extends T> other)
      • 비어있는 Optional 객체에 대해, 함수형 인자를 통해 생성된 객체 반환
      • 비어있는 경우에만 함수가 호출됨 → orElse 대비 성능상 이점을 가짐
    • orElseThrow(Supplier<? extends X> exceptionSupplier)
      • 비어있는 Optional 객체에 대해, 예외 던짐

Stream처럼 사용하기

  • Optional : 최대 1개의 원소를 가지고 있는 특별한 Stream이라 생각하자
  • Stream이 가진 map(), flatMap(), filter() 를 Optional도 가짐
    • map()
      • null-safe한 코드 구현 가능해짐
      • map() 을 활용한 개선 코드
      • /* 주문을 한 회원이 살고 있는 도시를 반환한다 */ public String getCityOfMemberFromOrder(Order order) { return Optional.ofNullable(order) .map(Order::getMember) //null 또는 Member객체 반환 .map(Member::getAddress) //null 또는 Address객체 반환 .map(Address::getCity) //null 또는Address 객체가 가지는 String city값 반환 .orElse("none"); //null이 반환될 때 default 값 설정 }
    • filter()
      • if조건문 없이 메서드 연쇄 호출만으로 읽기 편한 코드 작성 가능해짐
      • 함수형 인자의 리턴 값이 false인 경우 Optional을 비워버림. 그 이후 메소드 호출 의미 없어짐 !
      • Optional이 비어있거나 filter조건을 만족하지 않으면 연산 후에도 Optional.empty()가 됨
      • Optional이 값으로 채워져 있거나, 그 값이 filter조건을 만족하면 Optional은 그 값을 계속 가짐
      • filter() 를 활용하여 개선한 코드
        • order 가 null 이면 Optional.empty() 반환
        • filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000): filter 메소드는 주어진 조건을 만족하는지 확인
        • map(Order::getMember) : filter를 통과한 Order객체가 있으면 그 Order의 Member를 가져와서 Optional로 감싸기
      • public Optional<Member> getMemberIfOrderWithin(Order order, int min) { return Optional.ofNullable(order) .filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000) .map(Order::getMember); }