여행을 개발하다

[Java] Optional(Optional<T>) 본문

BackEnd/Java

[Java] Optional(Optional<T>)

yhtragramming 2021. 12. 31. 15:06

프로그래머로서 가장 많이 접하게 되는 오류 중 하나는 Null Pointer Exception 일 것이다.

나 또한 이 예외 처리를 하기 위해서 수많은 if 문을 사용했던 것 같다.

하지만 조건문이 많아지면 많아질수록, 코드가 복잡해지고 유지 보수가 어려워지는 것은 두말하면 잔소리다.

 

이번 프로젝트에서 자바 8의 새로운 문법들을 많이 배운 것 같은데, 그중 하나가 Null Pointer Exception을 깔끔하게 처리하기 위한 Optional이라는 래퍼 클래스이다.

 

1. Optional<T> 이란?

- Null Pointer Exception(이하 NPE)에 대한 말끔한 처리를 위해 Java8부터 등장한 래퍼(Wrapper) 클래스이다.

- Null 값에 대해 바로 예외 발생을 시키지 않고, 상황별로 대체값, 빈 Optional 객체를 지정하여 예외를 피할 수 있다.

 

 

2. 선언

(다음의 선언 방법 3가지에 포함된 것도 엄밀히 말하면 Optional의 주요 메서드이다)

① Optional.empty() - return Optional.empty()

- 말 그대로 비어있는 객체를 만들어준다.

- Null 값이 오는 혹은 올 수도 있는 변수의 자료형을 제네릭 타입으로 써준다.

Optional<T> maybeT = Optional.empty();  // T는 제네릭 타입을 의미한다.
Optional<String> maybeString = Optional.empty();  // String 타입의 Optional 빈 객체 생성
 

② Optional.of(T) - return T

- 명시한 값을 가지고 있는 Optional 객체를 생성한다.

- 만약 명시한 값이 null일 경우 NPE가 발생한다.

Optional<String> maybeString = Optional.of("안녕하세요");
log.info("maybeString : {}", maybeString); // maybeString : Optional[안녕하세요]
 

③ Optional.ofNullable(T) - return T

- 명시한 값이 null일 경우 Optional 빈 객체(Opotional.empty)를, 값이 null이 아닐 경우 명시한 값을 가지고 있는 객체를 생성한다.

Optional<String> maybeString = Optional.ofNullable("안녕하세요");
Optional<String> maybeString2 = Optional.ofNullable(null);
log.info("maybeString : {}, maybeString2 : {}", maybeString, maybeString2);
// maybeString : Optional[안녕하세요], maybeString2 : Optional.empty
 

 

3. 주요 메서드

① get() - return T

- Optional 객체에 들어있는 값을 리턴한다.

- 좀 더 정확하게는 wrapper class인 Optional을 언박싱(unboxing)하는 메서드이다.

- Optional 객체에 값이 들어가 있으면 값을 리턴하지만, 값이 없는 경우는 'NoSuchElementException' 예외를 발생시킨다.

 

아래 코드에서, 개발자가 'defaultValue '로 정의된 Integer형 변수가 null 인지 혹은 그렇지 않은지 확신할 수가 없어 Optional의 'maybeDefaultValue'을 선언했다고 가정한다(물론, 아래 코드에서는 명확하게 'defaultValue'는 null이 아니다).

 

이때, 비단 'defaultValue'가 null이 아니라고 해도 실제 담겨있는 값은 '-1'이 아니라 'Optional[-1]'의 Optional 객체이다. 만약 Integer 형태의 -1을 사용하려면 Optional 객체에서 실제 값인 '-1' 빼줘야 하는데, 이때 사용하는 메서드가 get()이다.

Integer defaultValue = -1;
Optional<Integer> maybeDefaultValue = Optional.of(defaultValue);
log.info("maybeDefaultValue : {}, maybeDefaultValue.get() : {}", 
                  maybeDefaultValue, maybeDefaultValue.get());
//maybeDefaultValue : Optional[-1], maybeDefaultValue.get() : -1

Integer defaultValue = null;
Optional<Integer> maybeDefaultValue = Optional.ofNullable(defaultValue);
log.info("maybeDefaultValue.get() : {}", maybeDefaultValue.get());
// NoSuchElementException 발생
 

 

② isPresent() - return boolean

- Optional 객체에 값이 들어있으면 true를, 비어있는 경우 false를 리턴한다.

Integer defaultValue = -1;
Optional<Integer> maybeDefaultValue = Optional.ofNullable(defaultValue);
log.info("maybeDefaultValue.isPresent() : {}", maybeDefaultValue.isPresent());
// maybeDefaultValue.isPresent() : true
		
defaultValue = null;
maybeDefaultValue = Optional.ofNullable(defaultValue);
log.info("maybeDefaultValue.isPresent() : {}", maybeDefaultValue.isPresent());
// maybeDefaultValue.isPresent() : false
 

 

③ isEmpty() - return boolean

- Optional 객체에 값이 들어있으면 false를, 비어있는 경우 true를 리턴한다. 정확히 isPresent() 메서드와 반대되는 값을 리턴한다.

Integer defaultValue = -1;
Optional<Integer> maybeDefaultValue = Optional.ofNullable(defaultValue);
log.info("maybeDefaultValue.isEmpty() : {}", maybeDefaultValue.isEmpty());
// maybeDefaultValue.isEmpty() : false
		
Integer defaultValue = null;
Optional<Integer> maybeDefaultValue = Optional.ofNullable(defaultValue);
log.info("maybeDefaultValue.isEmpty() : {}", maybeDefaultValue.isEmpty());
// maybeDefaultValue.isEmpty() : true
 

 

④ ifPresent(Consumer<? super T> consumer)

- Optional 객체의 값이 존재하면 실행되어야 하는 로직을 명시해 준다. 만약, Optional 객체의 값이 존재하지 않는다면 아무런 동작을 하지 않는다.

 

아래 코드는 AtomicInteger형의 변수 'defaultValue'의 값이 존재하면 해당 값을 1씩 증가시키는 로직이다.

 

하지만 'defaultValue'의 값은 null이므로, Optional로 박싱하면 'maybeDefaultValue'의 값은 'Optional.empty'가 된다. 이럴 경우, 'ifPresent(x -> x.getAndIncrement())' 부분은 아예 실행되지 않는다.

 

당연히 결과(defaultValue(after)) 값도 null이다.

AtomicInteger defaultValue = null;
log.info("defaultValue(before) : {}", defaultValue);
// defaultValue(before) : null

Optional<AtomicInteger> maybeDefaultValue =  Optional.ofNullable(defaultValue);
maybeDefaultValue.ifPresent(x -> x.getAndIncrement());
log.info("defaultValue(after) : {}", defaultValue);
// defaultValue(after) : null
 

하지만 'defaultValue'에 임의 값인 1을 넣어주면, ifPresent 안의 로직이 실행되어 값은 2가 된다.

AtomicInteger defaultValue = new AtomicInteger(1);
log.info("defaultValue(before) : {}", defaultValue);
// defaultValue(before) : 1

Optional<AtomicInteger> maybeDefaultValue =  Optional.ofNullable(defaultValue);
maybeDefaultValue.ifPresent(x -> x.getAndIncrement());

log.info("defaultValue(after) : {}", defaultValue);
// defaultValue(after) : 2
 

 

 orElse(T other) - return boolean

- 지정된 값이 존재하면 그 값을 리턴하고, 그렇지 않으면 인자로 전달된 값을 리턴한다.

Integer defaultValue = null;
Integer maybeDefaultValue =  Optional.ofNullable(defaultValue).orElse(3);
log.info("maybeDefaultValue : {}", maybeDefaultValue);
// maybeDefaultValue : 3	
 

 

⑥ orElseGet(Supplier<? extends T> other) - return T

- 지정된 값이 존재하면 그 값을 리턴하고, 그렇지 않으면 인자로 전달된 람다식의 결괏값을 리턴한다.

 

아래 코드에서는 'orElseGet()' 메서드 안에 정의된 람다식의 로직에 따라 'maybeDefaultValue'의 값에는 23(int1), 74(int2)가 곱해진 값 1702가 할당된다.

Integer defaultValue = 5;
Integer maybeDefaultValue =  Optional.ofNullable(defaultValue)
                    .orElseGet(()->{ int int1 = 23;
                    int int2 = 74;
                    return int1 * int2;
                    });
log.info("maybeDefaultValue : {}", maybeDefaultValue);
// maybeDefaultValue : 5
 
Integer defaultValue = null;
Integer maybeDefaultValue =  Optional.ofNullable(defaultValue)
        .orElseGet(()->{ int int1 = 23;
        int int2 = 74;
        return int1 * int2;
        });
log.info("maybeDefaultValue : {}", maybeDefaultValue);
// maybeDefaultValue : 1702
 

 

⑦ orElseThrow(Supplier<? extends X> exceptionSupplier) - return T

→ 지정된 값이 존재하면 그 값을 리턴하고, 그렇지 않으면 인수로 전달된 예외를 발생시킨다.

Integer defaultValue = 3;
Integer maybeDefaultValue;
try {
	maybeDefaultValue =  Optional.ofNullable(defaultValue)
			.orElseThrow(() -> new NoSuchAlgorithmException());
	log.info("maybeDefaultValue : {}", maybeDefaultValue);
}catch (NoSuchAlgorithmException nsae) {
	nsae.printStackTrace();
}
// maybeDefaultValue : 3
 
Integer defaultValue = null;
Integer maybeDefaultValue;
try {
	maybeDefaultValue =  Optional.ofNullable(defaultValue)
			.orElseThrow(() -> new NoSuchAlgorithmException());
	log.info("maybeDefaultValue : {}", maybeDefaultValue);
}catch (NoSuchAlgorithmException nsae) {
	nsae.printStackTrace();
}
// NoSuchAlgorithmException 예외 발생
 

지금까지 Optional 래퍼 클래스에 대해 알아보았다.

 

감사합니다 : )

'BackEnd > Java' 카테고리의 다른 글

[Java] TreeMap  (0) 2021.12.21
[Java] Iterator(반복자)  (1) 2021.09.02
[Java] Servlet, HttpServletRequest 인터페이스(Interface)  (0) 2021.08.27
[Java] Thread (스레드)  (0) 2021.08.15
[Java] Wrapper Class(래퍼 클래스)  (0) 2021.08.04
Comments