RxJava2 를 이용한 Collection 데이터 처리.
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[]{2, 5},new Integer[]{3, 8}, 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(1, 1001, "Doohyun Nam", 1) , new SubjectRelation(1, 1002, "Dolkin", 2) , new SubjectRelation(1, 1003, "hshawng", 1) , new SubjectRelation(1, 1004, "spKwon", 1) , new SubjectRelation(2, 1005, "Other Person1", 3) , new SubjectRelation(2, 1006, "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 를 이용하여 합계, 최소값, 최대값 등 통계 데이터를 구할 수 있었습니다.
2017/01/23 - [개발이야기/함수형 프로그래밍] - Stream API 활용편4 (리듀싱)
2017/01/25 - [개발이야기/함수형 프로그래밍] - Stream API 활용편5 (숫자형 스트림, 스트림 생산)
RxJava2 에서는 extension 라이브러리에서 이 기능을 수행할 수 있도록 도와줍니다.
1 2 3 4 5 | // 합계 Observable.range(0, 100).to(MathObservable::sumInt).blockingSingle(); // 평균 Observable.range(0, 100).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 |