[Java] Optional 정리

이창윤·2022년 8월 30일
0
post-custom-banner

Optional을 이해하기 전에 Wrapper 클래스가 무엇인지 알아야 한다.

Wrapper 클래스

기본 타입을 객체로 다루기 위해서 사용하는 클래스

기본 타입
(Primitive type)
래퍼 클래스
(Wrapper class)
char Character
int Integer
float Float
double Double
boolean Boolean
long Long
short Short
  • 래퍼 클래스는 각각의 타입에 해당하는 데이터를 인수로 전달받아, 해당 값을 가지는 객체로 만들어 준다.
  • 산술 연산을 위해 정의된 클래스가 아니므로, 인스턴스에 저장된 값을 변경할 수 없다.
  • 값을 참조하기 위해 새로운 인스턴스를 생성하고, 생성된 인스턴스의 값만을 참조할 수 있다.

박싱(Boxing)과 언박싱(Unboxing)

  • 박싱: 기본 타입의 값을 포장 객체로 만드는 과정
  • 언박싱: 포장된 객체에서 기본 타입의 값을 얻어내는 과정

예시

Integer num = 11; // 자동 박싱
int n = num; // 자동 언박싱

// 문자열(래퍼 클래스)을 정수형(기본 타입)으로 변환하고 싶을 때
String example = "123";
int ex = Integer.parseInt(example);
System.out.println(ex); // 123

Optional이란?

Java 8에 추가된 기능 중 하나로, NPE를 방지하기 위해 사용하는 래퍼(Wrapper) 클래스이다.

NPE (Null Pointer Exeption): 실제 값이 아닌 null값을 가지고 있는 객체/변수를 호출할 때 발생하는 예외

null값에 대한 처리를 제대로 하지 않으면 NPE이 발생하고, 코드 중간중간 null값을 체크하는 것도 번거롭다.
➡ 제네릭을 사용해서 null값일 수도 있는 변수를 wrapper클래스로 감싸주자.

public final class Optional<T> {
 
  // If non-null, the value; if null, indicates no value is present
  private final T value;
   
  ...
}

Optional에서 얻는 장점

  • NPE를 유발할 수 있는 null을 직접 다루지 않아도 된다
  • null값을 일일이 체크하지 않아도 된다.
  • 명시적으로 해당 변수가 null값일 수도 있다는 가능성을 표현할 수 있다. (제일 중요)



Optional<T> 사용법

Optional 객체의 생성

Optional 객체를 생성하려면 empty(), of(), ofNullable()을 사용해야 한다.

  • 변수의 값이 null일 가능성이 있으면 ofNullable()을 사용해야한다.
Optional<String> optVal = Optional.of(null); //NPE 발생
Optional<String> optVal = Optional.ofNullable(null); // OK
  • 변수를 기본값으로 초기화하고 싶을 때는 empty()를 사용한다.
Optional<String> optVal = null ;
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화

int, long, double일 경우

Optional로 감쌀 기본 타입이 int, long, double인 경우 Optional<Integer>, Optional<Long>, Optional<Double> 대신 OptionalInt, OptionalLong, OptionalDouble을 사용하면 불필요한 박싱/언박싱이 일어나지 않는다.

Optional 객체의 값 가져오기

  • get() 메소드로 Optional객체에 저장된 값에 접근할 수 있으나 null값이면 NoSuchElementException 예외가 발생한다 ➡ isPresent() 메소드를 사용하여 먼저 확인을 해주자
Optional<String> opt = Optional.ofNullable("하이");
if(opt.isPresent()) {
	System.out.println(opt.get());
}

// 메소드 참조 활용 ➡
opt.isPresent(System.out.println);
  • null값일 경우 orElse(), orElseGet(), orElseThrow() 등의 메소드를 이용하면 null값을 대체할 값을 지정할 수 있다. (isPresent() - get() 보다 유용하다)

  • 세 메소드는 값이 존재하면 그 값을 반환하고 값이 존재하지 않으면 반환할 값에 따라 나뉜다.

    • orElse(): 인수로 전달된 값을 반환
    • orElseGet(): 인수로 전달된 람다표현식의 결과값(Supplier)을 반환
    • orElseThrow(): 인수로 전달된 예외를 발생
Optional<String> optVal = Optional.of("abc");
  
String str1 = optVal.get(); // optVal에 저장된 값을 반환. null이면 예외 발생
  
String str2 = optVal.orElse(""); // optVal에 저장된 값이 null일 때는 ""를 반환
  
String str3 = optVal.orElseGet(() -> "def"); //optVal에 저장된 값이 null일 때 "def"를 반환하는 Supplier 객체를 반환
  
String str4 = optVal.orElseThrow(orElseThrow(()-> new Exception("값이 없습니다")) // optVal에 저장된 값이 null일 때는 예외가 메시지와 함께 발생

JpaRepository의 findBy() 메소드

(인텔리제이)control + B로 JpaRepository를 찾아가보면 CrudRepository를 extend하고 있는 것을 확인할 수 있다.

CrudRepository에 정의된 findById() 메소드는 반환값이 Optional이다!

반환값이 Optional이기 때문에 null인 경우 Optional의 메소드를 사용해 객체가 null 처리를 쉽게 할 수 있다.

Optional을 반환받기 때문에 별다른 처리가 필요없어도 보통 orElse(), orElseGet() 또는 orElseThrow() 로 값을 받아온다.

ArticleService.java 일부

@Transactional(readOnly = true)
public ArticleWithCommentsDto getArticleWithComments(Long articleId) {
        return articleRepository.findById(articleId)
                .map(ArticleWithCommentsDto::from)
                .orElseThrow(()-> new EntityNotFoundException("게시글이 없습니다 - articleId: " + articleId));
    }

참고 및 출처

함수형 인터페이스
Optional 클래스
래퍼 클래스

post-custom-banner

0개의 댓글