해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


기존 JAVA 에서는 변수에 값의 존재 여부를 명확하게 표현할 수 없었기 때문에 Null 검사를 해줬어야 했으며, 그럼에도 불구하고 NullPointerException 을 발생시키지 않기가 어려웠습니다.


그러나 JAVA8 에서는 Optional 이라는 Null 을 피하고자하는 새로운 개념이 생겼으며, 이 개념을 통해 어느정도 위의 문제를 해결하거나 비지니스 로직을 더 간소화 시킬 수 있었습니다. 


자세한 내용은 아래 포스팅을 참고! 



그러나, 아쉽게도 안드로이드에서는 Optional 을 API>=24 에서 사용할 수 있습니다.

하지만 언제나 JAVA8 을 대체하는 듯한 라이브러리가 있으니, RxJava 진영에서는 Maybe 를 지원합니다. Maybe 라는 이름이 생소해보일 수 있지만, 다른 언어에서는 Optional 대신 Maybe 라고 표기하는 곳도 있습니다.


Maybe 의 튜토리얼은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
// 빈 Maybe 생성.
Maybe<String> emptyMaybe = Maybe.empty();
// 유효 값 Maybe 생성.
Maybe<String> validMaybe = Maybe.just("Test1");
 
// 값이 유효할 때 로그 출력.
validMaybe.subscribe(System.out::println);
 
// 실제 데이터 출력.
String returnValue = validMaybe.blockingGet("Default Value");
cs


그러나 아직 RxJava2 의 버전이 높지 않아, 편의 메소드들이 많이 추가되지 않은 것으로 보입니다. 

그래서 Ndroid 프로젝트에서는 Maybe 를 조금 더 적극적으로 사용해보자는 취지로 MaybeUtil 을 제작하였습니다. 


편의의 기준은 현재 공부하고 있는 [JAVA8 in Action] 의 실무에서 많이 사용하는 예를 참고하여 작성하였습니다.


1. Null 을 포함하는 Just


Maybe 의 사용목적 자체가 값의 존재여부를 확신할 수 없기 때문에 사용한다고 볼 수 있지만, 정작 Maybe 를 생성하기 위해서는 null check 를 수행해야합니다. ㅡㅡ^


Maybe 에 있는 just 는 Null 을 포함할 수 없습니다.


MaybeUtil 에서는 이 이슈 해결을 위해 아래와 같은 튜토리얼을 제공합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
String test = "Test", nullValue = null;
 
// Null 을 포함할 수 있는 just!
MaybeUtil.JustNullable(test).subscribe(System.out::println);
 
Maybe<String> nullValueMaybe = MaybeUtil.JustNullable(nullValue);
nullValueMaybe.subscribe(System.out::println);
        
// 오직 비어있는 Maybe 일 때만 실행!
MaybeUtil.SubscribeEmpty(nullValueMaybe, () -> System.out.println("That value is null!!"));
 
// Maybe 가 유효하다면, 두번째 param, 아니라면 세번째 param 사용
MaybeUtil.Subscribe(nullValueMaybe, System.out::println, () -> System.out.println("That value is null!!"));
cs


2. Map 의 안정적 사용을 위한 Maybe 지원.


Map 에 데이터를 삽입하고, 이를 사용하기 위해서는 containsKey 메소드를 통해, Map 에 실제 데이터가 존재하는지 확인하여야 합니다. 이 문제 역시도 Maybe 의 철학과 같이 값의 존재여부 문제를 따져볼 수 있습니다.


아래의 튜토리얼을 참고하여, Map 사용에 대한 비지니스 로직을 줄여볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
HashMap<String, Integer> testMap = new HashMap<>();
testMap.put("key1"1);
testMap.put("key2"1);
testMap.put("key3"1);
 
// 맵에서 데이터 출력 시, Maybe 형태로 반환
ContainerUtil.JustInMap(testMap, "key1").subscribe(System.out::println);
 
// 맵에 데이터가 존재할 때 해야할 Consumer 를 정의할 수 있음.
ContainerUtil.RunMaybeInMap(testMap, "key1"System.out::println);
ContainerUtil.RunMaybeInMap(testMap, "key4"System.out::println);
cs



3. Box Object 의 parse 를 Maybe 지원.


String 클래스를 Integer 나 Double 등, Boxed Object 로 변환하는 parseXXX 메소드는 언제나 XXXFormatException 을 발생시킬 우려가 있습니다. 이 문제 역시도 String 을 각 Boxed Object 으로 변환하여 값의 존재를 따질 수 있냐는 문제로 볼 수 있습니다.


아래 튜토리얼은 안전한 변환을 지원합니다.


1
2
3
4
5
6
7
8
9
// Parse Integer
StringUtil.ParseInteger("1").subscribe(System.out::println);
StringUtil.ParseInteger("String Text").subscribe(System.out::println);
 
// Parse Boolean
StringUtil.ParseBoolean("false").subscribe(System.out::println);
 
// Parse Double
StringUtil.ParseDouble("0.0111111").subscribe(System.out::println);
cs


 2,3 번의 경우 MaybeUtil 에 모든 기능을 추가할 수 있었으나, 


MaybeUtil 의 추 후 확장성


이미 Ndroid 프로젝트에 존재하는 StringUtil 과 ContainerUtil 의 성격과 더 맞다는 점


에서 기능을 나누게 되었습니다.


자세한 내용은 Ndroid ReadMe 에서 확인하실 수 있습니다.

[https://github.com/skaengus2012/N-java/wiki/N-java-v0.2-wiki#maybeutil]


Maybe 를 통해 우리는 안드로이드의 비지니스 로직을 단순화시킬 수 있을 것이라 생각합니다.

Java8 의 새 라이브러리와 RxJava 는 거의 비슷하며, 어느곳에서도 익숙한 개발자가 되는 것은 조금의 노력으로 할 수 있을 것이라 생각합니다.


여러분들의 코드가 새 패러다임을 입고, 뷰티풀해지길 바랍니다. :-)




반응형
Posted by N'

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 에서도 언급을 하고 있습니다.


하지만 옛것의 익숙함을 조금 덜어내고, 새로운 패러다임에 익숙해진다면 여러분의 코드는 보다 아름다워질 수 있을 것입니다. :-)


자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기






반응형
Posted by N'

포스트의 제목은 현재 읽고 있는 책인 "JAVA8 IN ACTION"의 첫 목차입니다.


"JAVA8 을 눈여겨봐야 하는 이유 세번째 포스트" 로는 JAVA8 에 봐야할 추가된 개념 몇가지를 더 소개하고자 합니다.


1. 디폴트 메소드 (default method)


JAVA8 에서는 기존의 interface에 디폴트 메소드란 개념을 추가했습니다. "JAVA8 IN ACTION"의 내용에 따르면, 더 쉽게 변화할 수 있는 인터페이스를 만들 수 있도록 메소드를 추가했다고 합니다. (그러나 아마 stream의 개념을 기존 만들어진 collection 클래스들에 모두 추가할 수 없었던 문제가 가장 크기 않았을까요? interface에 메소드를 추가하면 모두 구현하던지 해야하니깐? ㅡㅡ^)


즉 이 메소드로 인하여, 미래의 변화에 유연하게 대처할 수 있게 되었습니다. (변화를 사랑하는 개발자가 됩시다.^^)  


사용방법은 interface에 default란 키워드를 붙여 사용할 수 있습니다. 이 default 메소드는 계속 함수형 프로그래밍에서 언급하던 동시실행에 대해서 안전합니다. 즉 이 개념을 사용하여 코드로 넘기는 함수로 많이 이용할 수 있을 것으로 보이네요.


다중상속의 문제가 있어보이지만 이를 피할 수 있는 방법이 있다고 합니다




2. Optional<T>


Optional<T>의 개념을 사용하여 NULL 예외를 피할 수 있다고 합니다. 이 객체는 값을 가지거나 안가질 수 있는 컨테이너 객체(이를테면 Collection 등..)로 값이 없는 상황을 명시적으로 처리 가능합니다. 


(즉 해당 기능으로 인하여, null에 대한 방어코드를 없앨 수 있기를 바랍니다.)




3. 구조적 패턴 매칭


객체지향 프로그래밍에서 극혐으로 하는 if-else-then 문제를 수학적으로 쉽게 명시할 수 있다고 합니다. (포스트 시, 업데이트 할 것)





자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기




반응형
Posted by N'