티스토리 뷰
Optional을 설명드리기 이전에 NPE(=NullPointerException)에 대해 간략히 말씀드리고 넘어가겠습니다.
NPE는 Java Application을 개발하면서 자주 접하는 Exception 중 하나입니다. 이 NPE를 피하기 위해서 Null 값을 검사하는 로직을 아래와 같이 구성해야 합니다.
List<String> names = getNames();
// NPE를 방지하기 위해 null 검사를 해야함
if(names != null){
names.sort();
}
하지만, 변수가 많아질 경우, Null을 검사해야 할 요소가 많아지기 때문에 꽤나 번거로워집니다. 이러한 불편한 점을 개선하고자 등장한 것이 Optional<T> 클래스입니다.
Optional 클래스
Java8 버전 이후부터 Optional<T> 클래스를 이용하여 NPE를 방지합니다.
Optional<T>는 String, Integer 클래스와 같은 Wrapper Class입니다. 따라서, 참조 타입의 특성으로 인하여 null을 넣을 수 있고 참조 하더라도 NPE가 발생하지 않게 도와줍니다.
public final class Optional<T> {
// If non-null, the value; if null, indicates no value is present
private final T value;
}
Optional은 value에 참조 값을 저장하기 때문에 null 이더라도 NPE가 발생하지 않고, 클래스이기 때문에 각종 메서드를 제공합니다.
Optional 활용 방법
Optional의 메서드
- Optional.empty() - 값이 Null인 경우
Optional<String> optional = Optional.empty();
System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false
이 Optional<T> 클래스를 살펴보면 내부에 static으로 EMPTY 객체를 미리 생성해서 가지고 있습니다.
이러한 이유로 빈 객체를 여러 번 생성해야 하는 경우에도 1개의 EMPTY 객체를 공유함으로써 메모리를 절약할 수 있습니다.
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
...
}
- Optional.of() - 값이 Null이 아닌 경우
데이터가 절대 Null이 아니라면 Optional.of()로 생성할 수 있습니다.
Optional<String> optinal = Optional.of("MyName")
- Optional.ofNullable() - 값이 Null일 수도 있고, 아닐 수도 있는 경우
어떤 데이터에 Null이 올 수도 있고 아닐 수도 있는 경우에 Optional.ofNullable로 생성할 수 있습니다. 이후, orElse() 또는 orElseGet()을 통해서 값이 없는 경우라도 안전하게 값을 가져올 수 있습니다.
Optional 예시 코드
사용법 1
Java8 이전에는 null 검사를 한 이후, null인 경우 새로운 객체를 생성해주어야 했습니다.
하지만, Java8 이후 Optional과 Lambda를 이용하여 해당 과정을 간단하게 표현할 수 있게 되었습니다.
// Java8 이전
List<String> names = getNames();
List<String> tempNames = list != null
? list
: new ArrayList<>();
// Java8 이후
List<String> nameList = Optional.ofNullable(getNames())
.orElseGet(() -> new ArrayList<>());
사용법2
null 검사를 하기 위한 아래와 같은 코드가 있다고 해봅시다. 이 코드는 if 조건이 여러 개 있어 꽤나 복잡해보입니다.
public String findPostCode() {
UserVO userVO = getUser();
if (userVO != null) {
Address address = user.getAddress();
if (address != null) {
String postCode = address.getPostCode();
if (postCode != null) {
return postCode;
}
}
}
return "우편번호 없음";
}
하지만, Optional을 이용하여 위 코드를 아래와 같이 표현할 수 있습니다.
Optional<UserVO> userVO = Optional.ofNullable(getUser());
Optional<Address> address = userVO.map(UserVO::getAddress);
Optional<String> postCode = address.map(Address::getPostCode);
String result = postCode.orElse("우편번호 없음")
사용법3
String name = getName();
String result = "";
try {
result = name.toUpperCase();
} catch (NullPointerException e){
throw new CustomUppaerCaseException();
}
위 코드 역시, 아래와 같이 변경이 가능합니다.
Optional<String> nameOpt = Optional.ofNullable(getName());
String result = nameOpt.orElseThrow(CustomUpperCaseException::new).toUpperCase();
정리
Optional은 NPE를 방지하기 위해 등장한 Wrapper Class입니다.
Optional은 여러 오버헤드가 있기 때문에 잘못 사용하게 되면 시스템 저하됩니다.
따라서, 메서드의 반환 값이 절대 Null이 아니라면 되도록 사용하지 않는 것을 권장합니다.
즉, Optional은 메서드의 결과가 Null이 될 수 있으며, Null에 의해 오류가 발생할 가능성이 매우 높을 때 반환 값으로만 사용해야 합니다.
orElse와 orElseGet의 차이
- orElse(): 파라미터로 값을 받습니다.
- orElseGet(): 파라미터로 함수형 인터페이스를 받습니다.
코드로 보는 orElse와 orElseGet의 차이점
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);
}
private String getUserEmail() {
System.out.println("getUserEmail() Called");
return "minsu@tistory.com";
}
/*
실행 결과
// 1. orElse인 경우
getUserEmail() Called
Empty
// 2. orElseGet인 경우
Empty
*/
처리 순서
orElse()
- Optional.onNullable로 "Empty"를 갖는 Optional 객체를 생성합니다
- getUserEmail()이 호출되어 반환 값(=minsu@tistory.com)을 orElse의 파라미터로 전달합니다.
- orElse()가 호출되었지만, "Empty"가 null이 아니기 때문에 "Empty"를 그대로 갖게 됩니다.
orElseGet()
- Optional.onNullable로 "Empty"를 갖는 Optional 객체를 생성합니다.
- getUserEmail() 함수 자체를 orElseGet의 파라미터로 전달합니다.
- orElseGet()이 호출되었지만, "Empty"가 null이 아니기 때문에 "Empty"를 그대로 가지면서 getUserEmail()이 호출되지 않습니다.
orElseGet에서는 파라미터로 받은 getUserEmail()이 null이 아니므로 .get에 의해 함수가 호출되지 않습니다.
만약 Optional의 값으로 null이 있다면, 다음과 같은 흐름에 의해 orElseGet의 파라미터로 넘어온 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";
}
- Optional.ofNullable로 null을 갖는 Optional 객체를 생성합니다.
- getUserEmail() 자체를 orElseGet 파라미터로 전달합니다.
- orElseGet()이 호출되며, 값이 null이므로 other.get()이 호출되어 getUserEmail()가 호출됩니다.
정리
orElse
- 파라미터로 값을 필요로 합니다.
- 값이 미리 존재하는 경우에 사용합니다.
orElseGet
- 파라미터로 함수를 필요로 합니다.
- 값이 미리 존재하지 않는 대부분의 경우에 orElseGet을 사용합니다.
'Language > JAVA' 카테고리의 다른 글
[JAVA]Thread란? (0) | 2024.09.09 |
---|---|
[JAVA]Object가 아닌 Objects 클래스 (0) | 2024.09.03 |
[JAVA]equals()와 hashCode()를 같이 재정의 하는 이유 (0) | 2024.08.30 |
[JAVA]JDK? JRE? JVM? (0) | 2024.08.28 |
[JAVA]JVM이란? (0) | 2024.08.28 |
- Total
- Today
- Yesterday