1. null과 관련된 문제
null과 관련된 문제들을 크게 2가지로 요약해보면
1) 런타임에 NPE(NullPointerException)라는 예외를 발생시킬 수 있다.
2) NPE 방어를 위해서 들어간 null 체크 로직 때문에 코드 가독성과 유지 보수성이 떨어진다.
이렇게 null과 관련된 문제들로 인하여 Optional이 등장하게 되었다.
2. Optional 이란?
Java8에서 Optional<T>는 null이 올 수 있는 객체를 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다. Optional을 최대 1개의 원소를 가지고 있는 특별한 Stream이라고 생각하면 편하다.
Optional 클래스는 아래와 같은 value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생하지 않으며, 클래스이기 때문에 각종 메소드를 제공해준다.
public final class Optional<T> {
// If non-null, the value; if null, indicates no value is present
private final T value;
...
}
3. Optional 사용의 이점
- NPE를 유발할 수 있는 null을 직접 다루지 않아도 된다.
- 수고롭게 null 체크를 직접 하지 않아도 되므로 코드가 간결하고 가독성이 높아진다.
- 명시적으로 해당 변수가 null일 수도 있다는 가능성을 표현할 수 있다. (불필요한 방어 로직 줄일 수 있음)
4. Optional 사용법
▶ Optional<T>: Optional 변수 선언
제네릭을 제공하므로 변수 선언 시 명시한 타입 파라미터에 따라 감쌀 수 있는 객체의 타입이 결정된다.
Optional<Order> maybeOrder; // Order 타입의 객체를 감쌀 수 있는 Optional 타입의 변수
Optional<Member> optMember; // Member 타입의 객체를 감쌀 수 있는 Optional 타입의 변수
Optional<Address> address; // Address 타입의 객체를 감쌀 수 있는 Optional 타입의 변수
<Optional 객체 생성 3가지 방법>
▶ Optional().empty(): null 을 담고 있는 Optional 객체 생성
null을 담고 있는(비어있는) Optional 객체를 얻어온다.
Optional<Member> maybeMember = Optional.empty();
▶ Optional().of(value): null이 아닌 객체(value)를 담고 있는 Optional 객체 생성
파라미터러 넘어온 객체 value가 항상 null이 아닐 경우에만 사용한다. 만약 value가 null이면 NPE를 던지므로 주의해서 사용해야한다.
Optional<Member> maybeMember = Optional.of(aMember);
▶ Optional().ofNullable(value): null일 수도 아닐수도 있는 객체(value)를 담고 있는 Optional 객체 생성
파라미터러 넘어온 객체 value가 null인지 아닌지 확신할 수 없는 경우 사용한다.
Optional.empty()와 Optional.of(value)를 합쳐놓은 메서드라고 생각하면 된다.
- 넘어온 객체(value) == null, Optional.empty()와 동일하게 빈 Optional 객체를 생성(NPE X)
- 넘어온 객체(value) != null, Optional().of(value)와 동일하게 null이 아닌 객체를 담아 Optional 객체를 생성
Optional<Member> maybeMember = Optional.ofNullable(aMember);
Optional<Member> maybeNotMember = Optional.ofNullable(null);
<Optional이 담고 있는 객체 접근하기>
Optional 클래스는 담고 있는 객체를 꺼내오기 위해서 다양한 인스턴스 메소드를 제공합니다. 아래 메소드들은 모두 Optional이 담고 있는 객체가 존재할 경우 해당 값(객체)를 반환한다. 반면에 Optional이 비어있는 경우(즉, null을 담고 있는 경우), 다르게 작동하므로 이 부분만 설명한다.
▶ get()
비어있는 Optional 객체에 대해, NoSuchElementException
▶ orElse(T other)
비어있는 Optional 객체에 대해, 넘어온 인자 값 반환
public void findUserEmailOrElse() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail)
.orElse(getUserEmail());
System.out.println(result);
}
private String getUserEmail() {
System.out.println("getUserEmail() Called");
return "mangkyu@tistory.com";
}
// 출력결과
//getUserEmail() Called
//Empty
- Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
- getUserEmail()가 실행되어 반환값을 orElse 파라미터로 전달
- orElse가 호출됨, "EMPTY"가 Null이 아니므로 "EMPTY"를 그대로 가짐
▶ orElseGet(Supplier<? extends T> other)
비어있는 Optional 객체에 대해, 넘어온 함수형 인터페이스 인자를 통해 생성된 객체를 반환
public void findUserEmailOrElse() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail)
.orElse(getUserEmail());
System.out.println(result);
}
public void findUserEmailOrElseGet() {
String userEmail = "Empty";
String result = Optional.ofNullable(userEmail)
.orElseGet(this::getUserEmail);
System.out.println(result);
}
// 출력결과
//Empty
- Optional.ofNullable로 "EMPTY"를 갖는 Optional 객체 생성
- getUserEmail() 자체를 orElseGet 파라미터로 전달
- orElseGet이 호출됨, "EMPTY"가 Null이 아니므로 "EMPTY"를 그대로 가지며 getUserEmail()이 호출되지 않음
public void findUserEmailOrElseGet() {
String result = Optional.ofNullable(null)
.orElseGet(this::getUserEmail);
System.out.println(result);
}
private String getUserEmail() {
System.out.println("getUserEmail() Called");
return "mangkyu@tistory.com";
}
// 출력결과
//getUserEmail() Called
// mangkyu@tistory.com
- Optional.ofNullable로 null를 갖는 Optional 객체 생성
- getUserEmail() 자체를 orElseGet 파라미터로 전달
- orElseGet이 호출됨, 값이 Null이므로 other.get()이 호출되어 getUserEmail()가 호출됨
- <orElse와 orElseGet>
public final class Optional<T> {
// 파라미터로 값을 받음
public T orElse(T other) {
return value != null ? value : other;
}
// 파라미터로 함수형 인터페이스(함수)를 받음
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
}
▶ orElseThrow(Supplier<? extends X> exceptionSupplier)
비어있는 Optional 객체에 대해서, 넘어온 함수형 인자를 통해 생성된 예외를 던짐
5. Optional을 통한 예외처리
▶ ifPresent(Consumer<? super T> consumer):
특정 결과를 반환하는 대신에 Optional 객체가 감싸고 있는 값이 존재할 경우에만 실행될 로직을 함수형 인자로 넘김
(예제)
// 파라미터로 들어온 member가 메모리에 이미 존재하는 회원인지 검증하는 함수
private void validateDuplicateMember(Member member) {
// optional 안의 값이 있으면(null이 아님) 예외 처리
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
- memberRepository.findByName(member.getName()): 파라미터로 들어온 member의 이름을 가진 회원이 DB에 있으면 그 회원 member객체를 Optional로 감싸 반환한다.
- .ifPresnt(): 만약 Optional로 감싸진 값이 존재하면(null이 아니면) 함수형 인자를 넘긴다.
- throw new IllegalStateException("이미 존재하는 회원입니다."): 예외 던짐
[참고] https://mangkyu.tistory.com/70
[참고] https://www.daleseo.com/java8-optional-after/
'Java > Java 학습' 카테고리의 다른 글
[JAVA] 예외 테스트 (jUnit5의 assertThrows) (0) | 2022.04.28 |
---|---|
[JAVA] JUnit5 기본 테스트 어노테이션(@Test, BeforeAll, @BeforeEach, @AfterAll, @AfterEach, @Disabled) (0) | 2022.04.28 |
[JAVA] UnitTest에서 사용하는 AssertJ의 AssertThat이란? (1) | 2022.04.28 |
[JAVA] Java8의 Stream 이란? (0) | 2022.04.28 |
[JAVA] Map이란? HashMap 이란? (0) | 2022.04.27 |