출처:
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 |