Java8 의 등장으로 드디어 Java를 사용하는 어플리케이션에서, 함수형 프로그래밍을 할 수 있게 되었습니다. Lambda 를 사용할 수 있으면서, Collection 처리를 선언형으로 처리할 수 있다면 매우 매력적일 것이라 생각합니다.


그러나 아쉽게도 안드로이드에서는 안좋은 소식이 있습니다. 강력한 Stream API 를 24 버전 이 후부터 사용할 수 있습니다. 하위호환성이란 문제를 생각해야하는 입장에서 24버전을 minimum 으로 맞춰두고 개발하기란 쉽지 않을 것 같습니다.


Call requires API level 24 (current min is 15): java.util.Collection#stream


하지만 이를 커버할 수 있는 방법 중 하나는 안드로이드에서 반응형 프로그래밍을 할 수 있는 RxAndroid2(RxJava2 포함) 를 사용하는 방법이라 할 수 있을 것 같습니다. 


글 작성 기준 (2017.02.27) RxJava2 가 새로 등장하였으며, 이를 기준으로 Stream 과 호환할 수 있는 메소드 사용법에 대해 포스팅해보려 합니다. (간단한 예제는 생략합니다. ㅡㅡ^)


일단 gradle 로 다음 RxJava 라이브러리들을 다운 받습니다. 


RxAndroid2 : https://github.com/ReactiveX/RxAndroid


Rx-java-extension : https://github.com/jacek-marchwicki/rx-java-extensions



1. RxJava2 에서의 매칭


Collection 클래스 내에서 매칭을 하여 결과를 알 수 있다는 것은 꽤 유용한 방법이었습니다. 


해당 결과를 가진 데이터가 존재하는가? 혹은 모든 데이터가 해당 결과를 만족하는가? 등 우리는 Java8 에서 단순히 선언만으로 데이터를 처리할 수 있었습니다.



RxJava2 에서도 크게 다르지는 않습니다.


다음과 같이 any 와 all 을 사용할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
List<Integer[]> testT = Arrays.asList(new Integer[]{25},new Integer[]{38}, new Integer[]{3,5});
            
// 일부 매칭.
Observable.fromIterable(testT).
        map(arrays -> Arrays.asList(arrays)).
        any(list -> !list.contains(8)).blockingGet();
 
// 전체 매칭
Observable.fromIterable(testT).
        map(arrays -> Arrays.asList(arrays)).
        all(list -> !list.contains(8)).blockingGet();
cs



2. flatMap 을 통한 컬렉션 join


컬렉션 내부의 제네릭객체를 join 할 수 있는 flapMap 에 대해서 소개한 적이 있습니다. 



RxJava2 에서도 방법은 크게 다르지 않습니다. 호환성있게 지원이 됩니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
List<MemberVo> memberVoList = Arrays.asList(
        new MemberVo(1"Doohyun Nam",28)
        , new MemberVo(2"Duckhyung Yuu"27));
 
List<PersonalInfoVo> personalInfoVoList = Arrays.asList(
        new PersonalInfoVo(1"My Way Functional Programming")
        , new PersonalInfoVo(2"My Way Spring FrameWork"));
 
Observable.fromIterable(memberVoList).
        flatMap(memberVo -> Observable.fromIterable(personalInfoVoList).
                                filter(personalInfoVo -> personalInfoVo.getMemberSubjectSn().equals(memberVo.getMemberSubjectSn())).map(personalInfoVo -> {
                            MemberPersonalVo memberPersonalVo = new MemberPersonalVo();
                            memberPersonalVo.setGoal(personalInfoVo.getGoal());
                            memberPersonalVo.setName(memberVo.getName());
                            return memberPersonalVo;
                        })).
        forEach(System.out::println); 
 
// Result
// Doohyun Nam : My Way Functional Programming
// Duckhyung Yuu : My Way Spring FrameWork
cs



3. Grouping 과 Partitioning 


컬렉션 데이터를 특정 조건으로 분류하고, 그 결과를 Map 으로 만들어주던 이 기능은 많은 비지니스 로직개선에 도움이 되었습니다. Collector 의 팩토리 메소드 몇개만 사용하면 쉽게 이용할 수 있었습니다.



그러나 RxJava2 에서는 이를 활용하기가 조금 까다롭습니다. Java8 의 Stream 처럼 collect 메소드를 지원하지만, 같이 사용가능한 Collectors 군의 팩토리 메소드들과 같은 유틸성 기능을 제공하지 않습니다.


대신에 toMap, toMultiMap, groupby 등의 메소드를 사용할 수 있습니다.


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
List<SubjectRelation> subjectRelationList = Arrays.asList(
                    new SubjectRelation(11001"Doohyun Nam"1)
                    , new SubjectRelation(11002"Dolkin"2)
                    , new SubjectRelation(11003"hshawng"1)
                    , new SubjectRelation(11004"spKwon"1)
                    , new SubjectRelation(21005"Other Person1"3)
                    , new SubjectRelation(21006"Other Person2"4)
            );
 
// create Map
Map<Integer, Collection<String>> mapTest = Observable.fromIterable(subjectRelationList).
           toMultimap(SubjectRelation::getCompanySubjectSn, SubjectRelation::getMemberName).
           blockingGet();
 
 
// only subscribe
Observable.fromIterable(subjectRelationList)
            .groupBy(SubjectRelation::getCompanySubjectSn).subscribe(group -> {
                  System.out.println(group.getKey());
                group.map(SubjectRelation::getMemberName).forEach(System.out::println);
            });
 
// create multi group
 Map<Integer, Map<Integer, Collection<String>>> doubleKeyMap = new HashMap<>();
            Observable.fromIterable(subjectRelationList).
                    groupBy(SubjectRelation::getCompanySubjectSn).
                    blockingSubscribe(group ->
                        doubleKeyMap.put(group.getKey(),
                                group.toMultimap(
                                        SubjectRelation::getOrganizationSubjectSn
                                        , SubjectRelation::getMemberName).blockingGet())
                    );
 
// partitioning
Map<Boolean, Collection<String>> partitioningMap = Observable.fromIterable(subjectRelationList).
             toMultimap(subjectRelation -> subjectRelation.getCompanySubjectSn().intValue() == 1
                 , SubjectRelation::getMemberName).
             blockingGet();
cs


세 번째 라인의 key 두 개를 이용하여 만든 맵의 경우 딱 맵까지 만들어주는 방법을 찾지는 못했습니다. 뭔가 우회해서 해보려고는 했으나, deadlock 현상을 보았습니다. ㅜㅡㅜ


Single 객체는 RxJava2 부터 더이상 null 을 허용하지 않겠다는 것으로 만든 객체로써, 사실 Single로 감싸게 된 맵이라도 실제 사용에는 큰 문제가 없어보입니다.


4. Math Library.


Java8 에서는 collect 나 reduce 를 이용하여 합계, 최소값, 최대값 등 통계 데이터를 구할 수 있었습니다.


RxJava2 에서는 extension 라이브러리에서 이 기능을 수행할 수 있도록 도와줍니다. 


1
2
3
4
5
// 합계
Observable.range(0100).to(MathObservable::sumInt).blockingSingle();
            
// 평균
Observable.range(0100).to(MathObservable::averageFloat).blockingSingle();
cs


5. Optional 대신 Maybe


Java8 에서는 더이상 null 을 허용하지 않겠다는 의도로 값의 존재여부를 표시할 수 있는 Optional 이란 개념이 생겼습니다. RxJava2 에서는 Maybe 를 통해서 이를 수행할 수 있습니다.


1
2
3
4
int number = 5;
 
Maybe<Integer> a = number% 5 == 0 ? Maybe.just(number) : Maybe.just(number);
a.subscribe(System.out::println);
cs


라이브러리나 플랫폼은 달라도 위에 있는 프로그래밍 패러다임의 계승은 비슷합니다.


아직 모든 기능을 알게 된 것은 아니지만, 안드로이드에서도 본격적으로 함수형 프로그래밍을 할 수 있을 것으로 보입니다.

반응형

'개발이야기 > 안드로이드' 카테고리의 다른 글

MVP 패턴을 통한 메모리 누수 관리  (0) 2016.11.24
WeakReference 테스트  (2) 2016.11.15
액티비티 개수 구하기  (0) 2016.11.15
안드로이드 DOZE 모드 만들기  (0) 2016.11.15
Posted by N'