본문 바로가기
java

또다시 Optional

by 오우지 2021. 12. 1.

스프링보다 자바에 대한 공부가 더 필요하다는 생각이 든다. 생 객체가 아니라 Optional을 통해서 JPA의 값을 받으면서 서비스 단에서 null처리를 하면서 든 생각이 이런식으로 사용하면 객체를 사용하지 않을 이유가 없는데 왜 굳이 Optional을 쓰고 있을까? 였고 결과적으로 Spring Data JPA Repository의 메서드는 null을 반환하는 것이 아닌 비어있는 컬렉션을 반환해주므로 굳이 Optional을 이용해서 감쌀 필요가 없었다.

Optional은 이전 포스팅에서도 말했듯이 자바8 에서 람다, 스트림 등과 함께 새로 나온 문법으로 null을 처리하는데 유용하게 쓰기 위한 하나의 포장지다.

 

return user.getAddress()

위의 코드에서 user에 address가 없다면 null pointer exception이 뜨게 된다. 

이를 막기 위해서 if문을 이용해서 null을 체크해주는 과정을 거쳐야 되는데 그 과정이 없다면 예상치 못한 곳에서 Null pointer exception이 뜨게 되고 그렇다고 너무 많은 if문을 남발하게 되면 코드의 가독성이 떨어지게 된다.

 

그래서 Optional이 나오게 됐다. Optional은 원소가 하나밖에 없는 Collection이나 Stream으로 생각하면 된다. Optional은 Stream이 가지고 있는 map()이나 flatMap(), filter()같은 메소드를 가지고 있다. Optional의 특징으로는

 

1. null을 직접 다루지 않아도 되고 

2. null체크를 직접 하지 않아도 된다.

3. null의 가능성을 표시할 수 있다.

 

Optional<Order> maybeOrder;

변수를 선언할 때 다음과 같이 maybe나 opt같은 접두어를 이용해 Optional 변수인 것을 나타내기도 한다.

 

Optional.empty() -> 빈 Optional을 만든다. Optional이 내부적으로 생성한 싱글턴 인스턴스이다.

Optional.of(value) -> null이 아닌 객체를 담고 있는 Optional 객체를 생성하는데 NPE를 던진다.

Optional.ofNullable(value) -> null인지 아닌지 확신할 수 없는 객체를 담고 있는 Optional을 선언

 

Map을 이용해 Order에서 City를 얻어내기 위한 코드이다.

public String getCityOfMemberFromOrder(Order order) {
	return Optional.ofNullable(order)
			.map(Order::getMember)
			.map(Member::getAddress)
			.map(Address::getCity)
			.orElse("Seoul");
}

이전에는 3중 for문을 이용해서 null을 체크해주고 마지막에 디폴트값을 설정해줄 수 있었다면 Optional을 사용하면 다음과 같이 간단하게 표현할 수 있다.

 

 

- ofNullable() 메서드를 호출해 Order객체를 Optional로 감싸주었다. 또한 Order객체가 null인 경우를 대비해 of 대신 ofNullable()을 사용했다.

- 3번의 메서드 연쇄 호출을 이용해 객체를 3번 변환했다.

- 마무리로 orElse를 통해 디폴트를 정해줬다.

 

filter()를 통해 if문의 필터링도 해줄 수 있다.

기존의 코드가 이것이라면

public Member getMemberIfOrderWithin(Order order, int min) {
	if (order != null && order.getDate().getTime() > System.currentTimeMillis() - min * 1000) {
		return order.getMember();
	}
}

코드의 가독성도 떨어질뿐더러 null을 return할 가능성도 저버리지 못한다.

하지만 만약 코드를 이렇게 쓴다면

public Optional<Member> getMemberIfOrderWithin(Order order, int min) {
	return Optional.ofNullable(order)
			.filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000)
			.map(Order::getMember);
}

filter()메소드는 넘어온 함수형 인자의 리턴 값이 false인경우 Optional을 비워버리므로 메서드 호출은 의미가 없어지게 된다. 대박이다.

 

orElse 말고 orElseGet을 사용하자. orElse는 만약 값이 null이라면 실행되지 않게 된다.

마지막으로 ifPresent() 메서드가 있다.

Optional<String> maybeCity = getAsOptional(cities, 3); // Optional
maybeCity.ifPresent(city -> {
	System.out.println("length: " + city.length());
});

이 메서드는 특정 결과를 반환하는 대신에 Optional 개체가 감싸고 있는 값이 존재할 경우에만 실행될 로직을 함수형 인자로 넘길 수 있다.

 

결론. 단순 null체크를 위한 프로그래밍을 위해서 Optional을 사용하는 것은 Optional이라는 값 비싼 객체를 위한 메모리 낭비일 뿐이다. 위의 중첩 if문 같이 가독성이 떨어지게 되는 작업이 필요할 때 Null체크를 위해 사용하는 것을 지향하자

 

아래 출처의 글을 읽고 다시 정리한 것에 불과합니다.

출처 : https://www.daleseo.com/java8-optional-effective/

https://homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/

 

'java' 카테고리의 다른 글

1. 생성자 대신 정적 팩터리 메서드를 고려하라  (0) 2023.02.19
함수형 프로그래밍  (0) 2021.12.15