본문 바로가기

프로그래밍/JAVA

자바8 Optional

출처: 

http://www.daleseo.com/java8-optional-before/

http://www.daleseo.com/java8-optional-after/

http://www.daleseo.com/java8-optional-effective/








Java8이 나오기 이 전에는 얼마나 힘들게 null 처리를 했었나..


아래 클래스를 살펴보자.

Order 클래스는 Member 타입의 필드를 가지고,

Member 클래스는 Address 타입의 필드를 가진다.

class Order {
private Long id;
private Date date;
private Member member;
}


class Member {
private Long id;
private String name;
private Address address;
}


class Address {
private String street;
private String city;
private String zipcode;
}


주문을 한 회원의 도시를 알고 싶다?

아래의 코드는 NPE 에 노출되어 있다...

public String getCityOfMemberFromOrder(Order order) {
return order.getMember().getAddress().getCity();
}



그럼 방어를 어떻게 할 것인가?

if 문을 계속해서 넣어줘야 할까..?

public String getCityOfMemberFromOrder(Order order) {
// return order.getMember().getAddress().getCity();
if (order == null) {
return "Seoul";
}
Member member = order.getMember();
if (member == null) {
return "Seoul";
}
Address address = member.getAddress();
if (address == null) {
return "Seoul";
}
String city = address.getCity();
if (city == null) {
return "Seoul";
}
return city;
}

불필요한 코드들을 넣어줘야 한다.. 너무나도 비효율적이다.



Java 8에서는 이러한 null을 대하는 접근 방식을 바꾸고자 했다.

함수형 언어의 접근 방식에서 영감을 받았다고 하는데 (함수형 언어는 차후에 공부해보자 @_@) 

여튼 등장한 녀석이 바로 java.util.Optional<T> 이다.





> Optional 객체 생성하기

- Optional.empty()

비어있는 Optional 객체를 얻는다.

싱글턴 인스턴스 




- Optional.of(value)

null이 넘어올 경우 NPE를 던지므로 주의해서 사용할 것.



- Optional.ofNullable(value)

null이 넘어올 경우 Optional.empty()를 넘긴다.








> Optional이 담고 있는 객체 접근하기

- get()

비어있으면 NoSuchElementException 발생한다. 




- orElse(T other)

비어있는 경우 넘어온 인자가 반환된다.




- orElseGet(Supplier<? extends T> other>

비어있는 경우 lazy하게 호출되어, 생성된 객체가 반환된다. 




- orElseThrow(Supplier<? extends X> exceptionSupplier)

비어있으면 예외를 던진다. 






사용해보자~!

public int someFunc1() {
String text = getText();
Optional<String> maybeText = Optional.ofNullable(text);
int length;
if (maybeText.isPresent()) {
length = maybeText.get().length();
} else {
length = 0;
}

return length;
}

public int someFunc2() {
String text = getText();
int length;
if (text != null) {
length = text.length();
} else {
length = 0;
}

return length;
}


위의 두 메서드를 보면 차라리 밑의 메서드가 나아 보이지 않나?

사고가 잘못됐다!!



그럼 위에 있던 getCityOfMemberFromOrder를 아래와 같이 작성할 것인가!?


public String getCityOfMemberFromOrder(Order order) {
Optional<Order> maybeOrder = Optional.ofNullable(order);
if (maybeOrder.isPresent()) {
Optional<Member> maybeMember = Optional.ofNullable(maybeOrder.get().getMember());
if (maybeMember.isPresent()) {
Optional<Address> maybeAddress = Optional.ofNullable(maybeMember.get().getAddress());
if (maybeAddress.isPresent()) {
Address address = maybeAddress.get();
Optional<String> maybeCity = Optional.ofNullable(address.getCity());
if (maybeCity.isPresent()) {
return maybeCity.get();
}
}
}
}
return "Seoul";
}





함수형 언어에 영감을 받아서 등장한 것이 Optional 이다.

바꿔보자.

public int someFunc1() {
String text = getText();
Optional<String> maybeText = Optional.ofNullable(text);
int length;
if (maybeText.isPresent()) {
length = maybeText.get().length();
} else {
length = 0;
}

return length;
}

public int someFunc2() {
String text = getText();
int length;
if (text != null) {
length = text.length();
} else {
length = 0;
}

return length;
}


public int someFunc3() {
return Optional.ofNullable(getText()).map(String::length).orElse(0);
}

매우 깔끔!!





그럼 getCityOfMemberFromOrder 얘도 바꿔보자.

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

null-safe한 코드이다. 














다른 null 처리 코드를 살펴보자.




아래 코드 처럼 비즈니스 로직을 처리하기 전에 항상 null 체크를 해주어야 했다.

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


아래처럼 null 체크 필요 없이 filter를 활용 해줄 수 있다.

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




HashMap가지고도 이용해보자.

Map<Integer, String> cities = new HashMap<>();
cities.put(1, "Seoul");
cities.put(2, "Busan");
cities.put(3, "Daejeon");


String city = cities.get(4); // null
int length = city == null ? 0 : city.length();
System.out.println(length);


// Optional
Optional<String> maybeCity = Optional.ofNullable(cities.get(4));
int length2 = maybeCity.map(String::length).orElse(0);
System.out.println("length: " + length2);





ArrayList 가지고도 해보자.

ArrayIndexOutOfBoundsException을 catch해서 Optional.empty()를 리턴하는 정적 팩토리 메서드를 만들어 버렸다.

null-safe하게 이용할 수 있다.

public void arraylistexample1() {
List<String> cities = Arrays.asList("Seoul", "Busan", "Daejeon");
String city = null;
try {
city = cities.get(3); // throws exception
} catch (ArrayIndexOutOfBoundsException e) {
// ignore
}
int length = city == null ? 0 : city.length(); // null check
System.out.println(length);


}

public void arraylistexample2() {
List<String> cities = Arrays.asList("Seoul", "Busan", "Daejeon");
Optional<String> maybeCity = getAsOptional(cities, 3); // Optional
int length = maybeCity.map(String::length).orElse(0); // null-safe
System.out.println("length: " + length);

// ifPresent를 이용하는 것도 좋은 방법이다. // maybeCity.ifPresent(city -> {

// System.out.println("length: " + city.length());
// });

}

public static <T> Optional<T> getAsOptional(List<T> list, int index) {
try {
return Optional.of(list.get(index));
} catch (ArrayIndexOutOfBoundsException e) {
return Optional.empty();
}
}











'프로그래밍 > JAVA' 카테고리의 다른 글

Java trouble shooting  (0) 2020.04.03
Thread Dump & Heap Dump  (0) 2019.08.31
WebFlux & Non-blocking  (0) 2019.04.22
Collection remove에 대해  (0) 2019.03.20
String, StringBuffer, StringBuilder  (0) 2019.03.19