Null 대신 Optional!
JAVA8 부터 지원하는 기능 중 특이한 녀석 중 하나는 Optional 입니다.
문자 그대로 선택을 내포하고 있는 개념적 모델은 비지니스 로직을 구현함에 있어서, Stream 과는 또 다른 의미로 변혁을 불러올 수 있습니다.
이 개념을 사용하면, 그동안 당했던 NullPointerException 에서 어느 정도 해결할 수 있으며 분기처리 (if 등) 을 간략하게 생략할 수 있습니다. 특히 Null 처리에 대하서 고민을 하지 않아도 된다는 것은 꽤 큰 의미를 줄 수 있습니다.
예를 들어 아래 코드를 한번 같이 볼까요?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class OptionalTestVo { private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } public static class A { private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } } public static class B { } } | cs |
임의로 만들어진 위의 VO 는 멤버 변수로 객체 A 를 가지고 있습니다.
객체 A 는 또 객체 B 를 가지고 있군요. 이 VO 를 한번 사용한다고 가정해보겠습니다.
1 2 3 4 5 6 7 8 9 10 | OptionalTestVo ov = new OptionalTestVo(); OptionalTestVo.A a = new OptionalTestVo.A(); if (ov != null) { if (ov.getA() != null) { if (ov.getA().getB() != null) { System.out.println(ov); } } } | cs |
객체 내부의 멤버 변수들 역시 객체이며, 값이 있을 수도 있고 없을 수도 있기 때문에 위와 같이 방어코드를 작성해줘야 합니다. 물론 꼼꼼하게 처리할 수 있을 수도 있지만, 비지니스 로직을 작성하는 환경이 언제나 베스트하여 모든 상태를 체크한다는 것은 꿈과 같은 일일 수 있습니다. ㅡㅡ^
값이 있을 수도 있고 없을 수도 있는 이 상태를 Optional 을 사용함으로써 명시할 수 있습니다.
Optional 의 튜토리얼은 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | String stringValue = "1"; // Optional 생성. Optional<String> stringOptional = Optional.of(stringValue); Optional<String> stringNullAbleOptional = Optional.ofNullable(stringValue); Optional<String> emptyOptional = Optional.empty(); // Optional 에서 데이터 꺼내기. // 값이 존재하지 않는 경우도 있기 때문에 위험!! String originalData = stringOptional.get(); // 값이 존재하지 않을 경우 default 값 출력 요청! String originlDefaultData = stringOptional.orElse("Default"); // 값이 존재하지 않을 경우 supplier // 일종의 조건부 실행 String originlDefaultSupplierData = stringOptional.orElseGet(() -> "Default"); // 값이 존재할 경우 데이터 출력!! stringOptional.ifPresent(System.out::println); // 값의 존재 유무 확인. stringOptional.isPresent(); | cs |
Optional 로 실제 데이터 객체를 감싸고, 이를 실제 데이터 객체가 필요할 때 default 값을 고려하여 반환하거나 값이 있을 때만 실행하는 등 값이 있을 때만을 고려하여 데이터를 출력할 수 있어보입니다.
Optional 을 이용하여, 실제 데이터 VO 를 아래와 같이 정의하면 Null 상태의 걱정을 하지 않아도 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public class OptionalTestVo { // 멤버 변수 자체를 Optional 로 가지는 방법! private Optional<A> a = Optional.empty(); // 이 때 필수라고 생각되는 데이터는 Optional 을 취하지 않는 것도 방법! private String requiredData; public String getRequiredData() { return requiredData; } public void setRequiredData(String requiredData) { this.requiredData = requiredData; } public Optional<A> getA() { return a; } public void setA(A a) { this.a = Optional.ofNullable(a); } public static class A { private B b; // 멤버변수가 Optional 은 아니지만, 출력하는 getter 를 Optional 로 랩핑 public Optional<B> getB() { return Optional.ofNullable(b); } public void setB(B b) { this.b = b; } } public static class B { @Override public String toString() { return "테스트"; } } } | cs |
위와 같이 제작된 VO 에 의해, 앞서 서술한 null 검사를 수행하던 첫 번째 로직은 다음과 같이 변경될 수 있습니다.
1 2 3 4 5 6 | OptionalTestVo ov = new OptionalTestVo(); OptionalTestVo.A a = new OptionalTestVo.A(); ov.setA(a); a.setB(new OptionalTestVo.B()); ov.getA().flatMap(OptionalTestVo.A::getB).ifPresent(System.out::println); | cs |
객체 ov 내부에 있던, Optional<A> 는 flatMap 을 통해 A 내부 Optional<B> 로 1차원 평준화할 수 있으며, 값이 존재할 때 콘솔로그를 출력하도록 하였습니다.
flatMap 을 통한 평준화 과정은 두 Optional [ex. Optional<A>, Optional<B>] 를 합치는 과정으로 두 Optional 중 한 가지라도 빈 값이라면, 빈 Optional 상태를 출력하게 됩니다.
Optional 는 Null 체크 외에도 Stream API 의 중간연산과 같이 map, filter 등을 지원합니다.
즉 비지니스 로직에서 빠질 수 없는 분기처리에 대한 연산을 Optional 을 통해 선언형으로 작성할 수 있음을 의미합니다.
다음은 Optional 의 기능을 응용하여 수행한 분기처리입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Integer weight = 70; String weightToString; if (weight > 50) { weightToString = "보통 체중"; } else { weightToString = "마른 체중"; } Optional.ofNullable(weight). filter(w -> (w > 50)). map(w -> "보통 체중"). orElse("마른 체중"); | cs |
이와 같이 Optional 을 이용하면 잠재적인 Null 에 의한 오류 혹은 예외에 대해 대비하여 로직을 구성할 수 있고 비지니스 로직 역시 간략화시킬 수 있음을 볼 수 있었습니다.
이전 버전 JAVA 에 익숙하다면, Null 이 없다는 것을 상상도 할 수 없을 것입니다.
JAVA 기본 라이브러리 역시 Optional 을 호환성 혹은 막대한 코드량에 의해 제대로 활용하지 못하고 있다고 JAVA8 in Action 에서도 언급을 하고 있습니다.
하지만 옛것의 익숙함을 조금 덜어내고, 새로운 패러다임에 익숙해진다면 여러분의 코드는 보다 아름다워질 수 있을 것입니다. :-)
|
'개발이야기 > 함수형 방법론' 카테고리의 다른 글
CompleteableFuture 를 이용한 비동기 처리 조합 (0) | 2017.03.24 |
---|---|
CompleteableFuture 를 이용한 비동기 처리 (0) | 2017.03.23 |
디폴트 메소드와 다중상속 (0) | 2017.03.13 |
JAVA8 에서의 인터페이스 변화 (디폴트 메소드) (2) | 2017.03.10 |
함수형 프로그래밍으로 리팩토링. (0) | 2017.02.21 |