본문 바로가기

백엔드 기술/Java

Optional<T> 클래스란 ?

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' 카테고리의 다른 글