Optional을 사용하게 된 계기
바야흐로 프로젝트에서 Repository 개발 단계였다. 기존 엔티티 단건 조회는 단순하게 엔티티로 반환하고 있었고, 담당한 UserRepository를 테스트하다가 NPE를 만나게 된다.
NPE(Null Pointer Exception)
개발자들이 가장 많이 마주치는 에러가 아닐까 싶다. (만나기. 싫어요. 제발). NPE는 Null 반환 자체도 문제지만 추적할 수 없어 오류 핸들링이 더 어렵다는 극악무도한 점이 있다. 아무튼 내 케이스에서 원인은 없는 User 엔티티를 반환하려다 보니 발생한 에러였고, 이를 좀 더 쾌적하게 핸들링 하기 위해 Optional을 처음 사용하게 되었다. 기존 다른 코드에도 엔티티 단건 조회는 전부 엔티티를 반환하고 있었기에 이를 계기로 안전하게 Optional로 한번 감싸서 반환하게 되었다.
Null Pointer Exception
그래서 Optional이 뭔데?
Java 8부터 Optional<T> 클래스를 사용해 NPE를 방지할 수 있도록 도와준다. Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스이며, 참조하더라도 NPE가 발생하지 않도록 도와준다. 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;
...
}
Optional과 ElseThrow
Java 10에서 추가된 Optional 클래스의 elseThrow() 메서드를 사용하면, 값이 존재하지 않을 때 원하는 예외를 던질 수 있다. 이를 통해 값이 없을 때 자신만의 예외를 정의하고, 해당 예외를 던지는 로직을 더욱 간결하게 작성할 수 있다.
import java.util.Optional;
public class OptionalElseThrowExample {
public static void main(String[] args) {
String nullableValue = null;
Optional<String> optionalValue = Optional.ofNullable(nullableValue);
// 값이 존재하는 경우 출력, 값이 없으면 CustomException 던짐
String result = optionalValue.orElseThrow(() -> new CustomException("Value is absent."));
System.out.println("Value exists: " + result);
}
}
// CustomException 클래스 정의
class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
Optional은 값이 존재하지 않을 수도 있는 상황에서 유용하며, elseThrow() 메서드를 이용하여 원하는 예외를 던질 수 있다. 이를 통해 코드의 가독성과 안정성을 높이며, NullPointerException과 같은 예외를 방지할 수 있다.
Optional과 .map() 체이닝
map() 메서드는 Optional 객체 안의 값을 변환하거나 수정할 때 사용되며, 원본 Optional의 값이 존재할 경우에만 함수를 적용하고 그렇지 않을 경우에는 빈 Optional을 반환한다.
import java.util.Optional;
public class OptionalMapExample {
public static void main(String[] args) {
String name = "John Doe";
Optional<String> nameOptional = Optional.ofNullable(name);
// .map()을 사용하여 문자열을 대문자로 변환
Optional<String> uppercaseNameOptional = nameOptional.map(String::toUpperCase);
// 값이 존재하는 경우 대문자 이름 출력
uppercaseNameOptional.ifPresent(uppercaseName -> System.out.println("Uppercase Name: " + uppercaseName));
}
}
.map() 메서드는 체이닝을 통해 여러 번 사용할 수 있다. 이를 활용하여 여러 단계의 변환 또는 연산을 수행할 수 있다. 만약 중간에 하나라도 값이 null이라면 최종적으로 빈 Optional이 반환된다.
import java.util.Optional;
public class OptionalMapChainingExample {
public static void main(String[] args) {
String name = "John Doe";
Optional<String> nameOptional = Optional.ofNullable(name);
// 이름을 대문자로, 그리고 "HELLO, "를 추가
Optional<String> greetingOptional = nameOptional
.map(String::toUpperCase)
.map(uppercaseName -> "HELLO, " + uppercaseName);
// 최종 결과 출력
greetingOptional.ifPresent(greeting -> System.out.println("Greeting: " + greeting));
}
}
IsPresent()와 IfPresent()
isPresent()와 ifPresent() 메서드는 Optional 객체의 값의 존재 유무를 확인하고, 값이 존재할 때 해당 값을 안전하게 처리하는 데에 사용된다.
isPresent() 메서드는 Optional 객체의 값의 존재 유무를 확인하는 메서드로, 값이 존재하는 경우에는 true를, 값이 없는 경우에는 false를 반환한다.
import java.util.Optional;
public class IsPresentExample {
public static void main(String[] args) {
String name = "John Doe";
Optional<String> nameOptional = Optional.ofNullable(name);
// isPresent()를 사용하여 값의 존재 유무 확인
if (nameOptional.isPresent()) {
System.out.println("Name exists: " + nameOptional.get());
} else {
System.out.println("Name is absent.");
}
}
}
ifPresent() 메서드는 Optional 객체의 값이 존재할 때만 해당 값을 처리하는 메서드로, 값이 존재하는 경우에는 인자로 전달된 Consumer 함수가 실행되며, 값이 없는 경우에는 아무런 동작도 하지 않는다.
import java.util.Optional;
public class IfPresentExample {
public static void main(String[] args) {
String name = "John Doe";
Optional<String> nameOptional = Optional.ofNullable(name);
// ifPresent()를 사용하여 값의 존재 유무 확인 및 처리
nameOptional.ifPresent(value -> System.out.println("Name exists: " + value));
}
}
'💻dev > 🌱Java+Spring' 카테고리의 다른 글
Spring | @Builder, @NoArgsConstructor 그리고 푸른 수염의 @AllArgsConstructor (0) | 2023.08.09 |
---|---|
Java | Lombok의 @Builder와 @Builder.Default 알아보기 (0) | 2023.08.09 |
Querydsl | 동적 쿼리와 성능 최적화 조회 - Builder, Where절 파라미터 (0) | 2023.05.17 |
Querydsl | 수정, 삭제 벌크 연산하기 (0) | 2023.05.16 |
Querydsl | 동적 쿼리 - BooleanBuilder, Where 다중 파라미터 (0) | 2023.05.16 |