1. NullPointerException 그리고 Optional 의 필요성
대부분의 개발자들은 한번씩은 NullPointerException(NPE) 로 인해 고통받은 경험이 있을것이다.
다음과 같은 클래스들이 있다고 가정하자.
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
}
주문한 사람의 Zipcode를 알려주세요"라는 간단한 요청을 한다고 가정했을때(Order.getMember().getAddress().getZipCode) Order가 잘못입력되어 null 값이 들어오면 정말 끔찍할 일이 발생할것이다. 이런 일을 막기위해 보통 우리는 다음과 같은 방법을 사용했을 것이다.
if(order!=null){
// null 처리
member = order.getMember();
if(member != null){
address = member.getAddress();
if(address !=null){
zipCode = address.getZipcode();
}
}
}
와 같은 if문 구조나
try{
member = order.getMember();
address = member.getAddress();
zipCode = address.getzipcode();
}catch (NullPointerExceptione e){
}
와 같은 예외처리 구조를 생각했을 것이다.
두 방식 모두 NPE는 잡을 수 있지만, NULL을 처리하는 코드가 기존의 비즈니스 로직의 코드에 수십 줄로 길 if문과 try catch 문으로 지저분한 코드로 뒤덮이는 상황이 일어난다. (내가 원하는 Entity가 10개를 더 들어가야 나오는 상황을 생각하면 if문 들여쓰기를 10번이나 하는 어마무시한 코드를 봐야 할것이다.) 게다가 함수형 프로그래밍 마저 깨진 것을 볼 수 있다. 이러한 문제를 해결해주는 것이 바로 Optional 이다.
2. Optional 이란?
Optional은 JAVA 8에 추가된 새로운 문법으로 java.util.Optional 에 정의되어 있다.
간단히 정리하자면 Optional은 NULL일지 아닐지 모르는 데이터를 저장하는 클래스다. 이 말은 "NULL에 대한 예외처리는 Optional 클래스가 대신 처리해줄테니 프로그래머는 비즈니스 로직 개발에 집중하라" 라고도 말할수 있습니다.
이 Optional 로 누릴수 있는 장점은 다음과 같습니다.
1) 프로그래머는 널에 대한 예외처리를 직접 하지 않고 Optional에 위임할수 있습니다.(코드 간결해짐)
2) 함수형 프로그래밍 구조를 유지할수 있습니다.
3. Optional 의 기본 사용법
3-1 Optional 변수 선언
Optional은 제너릭을 제공하기 때문에 변수를 선언할때 다음과 같이 명시적으로 타입을 명시해야합니다.
Optional <Order> maybeOrder;
Optional <Address> optAddress;
Optional을 사용할때에는 opt 나 maybe와 같은 키워드를 넣어서 이 변수가 Optional 임을 명시적으로 알려주는 방식을 많이 사용합니다.
3-2 Optional 인스턴스 생성
Optional 인스턴스를 3가지 방식으로 만들 수 있습니다.
1) Optional.empty(); //비어있는 Optional 객체를 생성합니다.(Optional 에서는 절대로 null을 명시적으로 사용하지 않고 이 방법을 사용합니다.)
2) Optional.of(a); // NULL 이 아닌 객체가 담긴 Optional 객체를 생성합니다. 만약 인수로 NULL 값이 들어오면 NPE 이 발생합니다.
3) Optional.ofNullable(b) // NULL 인지 아닌지 모르는 객체가 담긴 Optional 객체를 생성합니다. NULL 이 들어오더라도 NPE 가 발생하지 않고 비어있는 Optional 객체가 return 됩니다.
3-3 Optional 객체에 접근
Optional 객체에 저장된 객체에 접근하는 방법입니다.
1) get() // Optional 에 저장된 객체를 return합니다.(비어있는 경우 NPE가 발생합니다.)
2) orElse() // Optional에 저장된 객체를 return합니다. 비어있는 경우에 인수에 있는 객체를 return 합니다.
3) orElseGet() //Optional 저장된 객체를 return 합니다. 비어있는 경우에만 객체를 return 하는Supplier를 실행합니다.
2, 3번은 비슷해보이고 실제로 객체가 비었을 때는 똑같이 동작한다. 하지만 객체가 비어있지 않을 경우에 차이가 발생한다.
---결과----
보면 첫번째 경우에는 비어있지 않았음에도 getDefaultname을 통해 객체가 생성된 것을 볼수 있다. 미미한 차이지만 실제 웹서비스 환경이라면 소중한 서버의 리소스가 낭비되는 것이다.
4. Optional 을 Optional 답게 사용하기
String text = getText();
Optional<String> maybeText = Optional.ofNullable(text);
int length;
if (maybeText.isPresent()) {
length = maybeText.get().length();
} else {
length = 0;
}
많은 사람들이 위와 같이 Optional을 사용했을 것이다. (심지어 싸피에서도 이렇게 써있는 코드를 제공했다. )
String text = getText();
int length;
if (text != null) {
length = maybeText.get().length();
} else {
length = 0;
}
하지만 이렇게 쓰는 것은 Optional 을 안쓰는 것만 못하다.(래퍼 클래스로 인한 오버헤드) Optional을 썼으니 우리는 Optional 안에 값이 널이든 아니든 우리가 할 비즈니스 로직을 똑같이 수행하면된다.( Stream 함수형 프로그래밍이라고 생각하자. )
즉, 이렇게 쓰면안되고
/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
Optional<Order> maybeOrder = Optional.ofNullable(order);
if (maybeOrder.isPresent()) {
Optional<Member> maybeMember = Optional.ofNullable(maybeOrder.get());
if (maybeMember.isPresent()) {
Optional<Address> maybeAddress = Optional.ofNullable(maybeMember.get());
if (maybeAddress.isPresent()) {
Address address = maybeAddress.get();
Optinal<String> maybeCity = Optional.ofNullable(address.getCity());
if (maybeCity.isPresent()) {
return maybeCity.get();
}
}
}
}
return "Seoul";
}
이런 식으로 사용하는것이다.(map 은 인수에 함수를 적용해서 객체를 변환하는 메소드이다.)
public String getCityOfMemberFromOrder(Order order) {
return Optional.ofNullable(order)
.map(Order::getMember)
.map(Member::getAddress)
.map(Address::getZipCode)
.orElse("Seoul");
}
'JAVA' 카테고리의 다른 글
JDBC, MyBatis (0) | 2020.08.27 |
---|---|
JAVA 8 - 람다 추가 (0) | 2020.08.15 |
웹서버 vs WAS (0) | 2020.08.07 |
StringBuilder vs StringBuffer (0) | 2020.07.02 |
Java String (0) | 2020.07.02 |