Optional<T> 클래스
Java 프로그래밍을 하다보면 객체의 값이 null 값일 경우,
그 객체를 참조할 때 발생하는 #NullPointerException(NPE)가 자주 발생한다.
[#NullPointerException(NPE)]
정의 : null 때문에 발생하는 Runtime Exception.
원인 : null 자체의 의미가 모호해 다양한 파생 에러가 발생.
예시
#1. null 객체의 인스턴스 메서드를 호출
Object obj = null; obj.toString();
#2. null 객체의 필드 액세스 또는 수정
Person p = null; p.name = "nhj";
#3. 빈 배열 객체에 길이 속성을 가져올 때
int[] arrayInts = null; arrayInts.length;
#4. 배열 슬롯 중 null인 배열에 액세스하거나 수정할 때
String[] arrayStr = new String[2];
arrayStr[0] = "aa";
arrayStr[1].toString();
#5. Throwable 값인 것처럼 null을 던질 때
throw null;
Optional은 null이 될 수도 있는 객체를 감싸는 Wrapper 클래스 타입으로, NPE를 방지해준다.
Optional<T> 클래스는 제네릭 클래스로 T 타입의 객체를 감싸는 Wrapper Class이다.
즉 Optional 타입의 객체는 모든 타입의 참조변수를 담을 수 있다.
[사용 이유]
1. Runtime 시 NPE 예외 발생할 수 있다.
2. NPE를 방어하기 위해서는 null 체크를 해야하는데 이는 코드 가독성과 유지 보수성이 떨어진다.
[ 사용 예제 ]
/* 주문 */
public class Order {
private Long id;
private Date date;
private Member member;
// getters & setters
}
/* 회원 */
public class Member {
private Long id;
private String name;
private Address address;
// getters & setters
}
/* 주소 */
public class Address {
private String street;
private String city;
private String zipcode;
// getters & setters
}
위와 같은 코드가 있고, '어떤 주문을 한 회원이 살고 있는 도시'를 찾기 위한 메서드가 있다고 가정하자.
/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
return order.getMember().getAddress().getCity();
}
위의 코드에서
만일 order, order.getMember(), getAddress(), getCity() 가 null 일 경우
그 null 값에 대한 처리를 아래와 같이 해줘야만 한다.
public String getCityOfMemberFromOrder(Order order) {
if (order != null) {
Member member = order.getMember();
if (member != null) {
Address address = member.getAddress();
if (address != null) {
String city = address.getCity();
if (city != null) {
return city;
}
}
}
}
return "Busan"; // default
}
이는 가독성이 떨어지고 유지 보수에서도 취약한 모습이 있다.
하지만 여기서 Optional을 사용하게되면
/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
return Optional.ofNullable(order)
.map(Order::getMember)
.map(Member::getAddress)
.map(Address::getCity)
.orElse("Busan");
}
위와 같이 더 직관적이며 간결한 코드로 완성할 수 있다.
[장점]
1. NPE를 유발할 수 있는 null을 직접 다루지 않아도 된다.
2. if문으로 null을 체크하지 않아도 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다.
3. 명시적으로 해당 변수가 null 일 수 있다는 가능성을 표현할 수 있다.
[Optional 생성법]
Optional 객체를 생성할 때는 of() 또는 ofNullable()을 사용한다.
String str = "David Han";
Optional<String> str1 = Optional.of(str);
Optional<String> str2 = Optional.of("David Han");
참조변수의 값이 null일 가능성이 있으면, of()대신 ofNullable()을 사용해야 한다.
of()는 null 일 경우, NullPointerException이 발생한다.
Optional<Object> obj1 = Optional.of(null); // NPE 발생
Optional<Object> obj2 = Optional.ofNullable(null); // ERROR 발생 X
또한 Optional<T> 타입의 참조변수 값을 기본값으로 초기화 할 때는 = null로도 가능은 하지만
null을 직접적으로 다루는 것은 위험하므로 Optional.empty() 메서드를 사용한다.
Optional<String> optional = null;
Optional<String> optional1 = Optional.empty();
[결론]
Optional은 NPE를 방지할 수 있는 Wrapper 클래스 타입이다.
[ 생성 ]
1. of() : Optional 객체가 null이 아닌게 확실한 경우
2. ofNullable() : Optional 객체가 null 일 가능성이 있을 경우
public Member updateMember(Member member) throws Exception{
Member findMember = findMemberById(member.getMemberId());
Optional.ofNullable(member.getDisplayName())
.ifPresent(displayName -> findMember.setDisplayName(displayName));
Optional.ofNullable(member.getAboutMe())
.ifPresent(aboutMe->findMember.setAboutMe(aboutMe));
Optional.ofNullable(member.getPhoneNumber())
.ifPresent(phone->findMember.setPhoneNumber(phone));
Optional.ofNullable(member.getProfilePicture())
.ifPresent(profile->findMember.setProfilePicture(profile));
return memberRepository.save(findMember);
}
3. Optional.empty() : Optional 객체의 값이 기본값(null)으로 초기화 할 때
[ 기타 사용 메서드 ]
1. .get() : Optional 객체에 저장된 값을 불러온다. (null 값일 경우 NoSuchElementException 발생)
2. .orElse(N) : 비어있는 Optional 객체면 N 내용 출력.
3. .orElseGet(()->new String("N"))) : 비어있는 Optional 객체를 대체할 값을 람다식을 통해 지정 가능.
4. .orElseThrow(NoSuchElementException::new)) : Optional 객체가 null 값일 경우 지정한 예외 던짐.
public CommentDrink findCommentDrinkById(long commentDrinkId) {
Optional<CommentDrink> cd = repository.findById(commentDrinkId);
CommentDrink result = cd.orElseThrow(()-> new BusinessLogicException(ExceptionCode.DATA_IS_EMPTY));
return result;
}
[1-4 예시]
public class Main {
public static void main(String[] args) throws Throwable {
String str = "David Han";
Optional<String> str1 = Optional.of(str);
System.out.println("str1 = " + str1.get());
Optional str2 = Optional.empty();
System.out.println("str2 = " + str2.orElse("hi"));
System.out.println("str2 = " + str2.orElseGet(()-> new String("hello")));
System.out.println("str2 = " + str2.orElseThrow(NoSuchElementException::new));
}
}
결과

5. isPresent() : Optional 객체의 값이 null이 아니면 true, null이면 false 반환
Optional str2 = Optional.empty();
if(str2.isPresent()){
System.out.println("true");
}else System.out.println("false");
6. ifPresent() : Optional 객체의 값이 null이 아니면 주어진 람다식을 실행, null이면 아무것도 하지 않음.
Optional.ofNullable(str2).ifPresent(System.out::println);
'백엔드 기술 > Java' 카테고리의 다른 글
Synchronized 키워드의 이해 (0) | 2023.04.29 |
---|---|
얕은 복사 VS 깊은 복사 (0) | 2023.04.27 |
Multi-Thread & Multi Process (0) | 2023.04.20 |
SingleTon 디자인 패턴 (0) | 2023.04.20 |
컴파일(Compile)과 런타임(Runtime) (0) | 2023.04.20 |