Language/Java

RxJava - 3 Operators 이어서

park_juyoung 2019. 1. 26. 18:52

예제를 통해 좀 더 operator에 대해서 설명하겠습니다.



아래와 같은 매소드가 있다고 가정합니다.

1
2
// text에 기반한 검색 된 website URL 리스트를 반환
Observable<List<String>> query(String text); 
cs


text 검색을 통한 결과를 보여주는 시스템을 만들려고 합니다.

이전에 본 매소드를 이용하면 다음과 같은 결과가 나올 수 있습니다.


1
2
3
4
5
6
query("Hello, world!")
    .subscribe(urls -> {
        for (String url : urls) {
            System.out.println(url);
        }
    });
cs


이러한 구조는 데이터 스트림을 변환하는 기능이 없어지므로 매우 불만스러울 수 있습니다. 만약 각각의 URL에 대해서 수정을 하고 싶다면 이것을 Subscriber 에서 진행하여야 합니다.


 map()에서 urls -> urls을 만들 수 있었지만 map() 은 안에서 for-each 루프를 생성합니다.

 from

Observable.from() 은 항목 컬렉션을 가져와서 각각을 한 번에 하나씩 방출합니다.

1
2
Observable.from("url1""url2""url3")
    .subscribe(url -> System.out.println(url));
cs


이것이 어떤식으로 좀 더 나은지 보겠습니다.


1
2
3
4
5
query("Hello, world!")
    .subscribe(urls -> {
        Observable.from(urls)
            .subscribe(url -> System.out.println(url));
    });
cs



for-each 루프는 없지만 코드는 보기 좋지 않을 뿐더러 수정하기도 힘듭니다.


 더 나은 방법

Observable.flatMap() 은 Observable의 결과를 다른 스트림으로 방출한다.


1
2
3
4
5
6
7
8
query("Hello, world!")
    .flatMap(new Func1<List<String>, Observable<String>>() {
        @Override
        public Observable<String> call(List<String> urls) {
            return Observable.from(urls);
        }
    })
    .subscribe(url -> System.out.println(url));
cs


위에 코드를 람다로 표현하게 되면 아래와 같습니다.


1
2
3
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .subscribe(url -> System.out.println(url));
cs


왜 다른 Observable로 돌려주는건가? 

여기서 중요한 핵심 개념은 새로운 Observable을 Subscriber가 보는 것입니다.

이것은 List<String>을 받는 것이 아니라 일렬의 개인적인 String을 돌려 받습니다.

이 부분은 이해하기가 어려워 설명이 이상한점 양해 바랍니다.


조금더 예를 들어 보겠습니다.


아래와 같은 메소드가 있다고 가정하겠습니다.


1
2
// 해당 URL에 대한 title을 반환한다. 없다면 null를 반환
Observable<String> getTitle(String URL);
cs


이 메소드를 통해 각 웹사이트의 제목을 출력하고 싶습니다. 하지만 이 메소드는 한번에  한 URL에 대해서만 작동하며

String을 반환하지 않고 Observable이 String을 반환합니다.


flatMap() 을 사용하면 이 문제를 쉽게 해결 할 수 있습니다. URL의 목록을 개개의 아이템으로 나눈후 flatMap() 안에서 getTitle()을

하면 됩니다.


1
2
3
4
5
6
7
8
9
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(new Func1<String, Observable<String>>() {
        @Override
        public Observable<String> call(String url) {
            return getTitle(url);
        }
    })
    .subscribe(title -> System.out.println(title));
cs


한번더 람다를 통해 단순화.


1
2
3
4
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .subscribe(title -> System.out.println(title));
cs



이외의 Operators

지금까지 두 개의 연산자만 살펴 봤지만 더 많은 연산자가 있습니다. 
예제를 통해 살펴 보도록 하겠습니다.
getTitle() 은 URL 404라면 null을 반환합니다. 우리는 null을 반환하는 것을 원하지 않습니다.
여기 null 경우를 걸러 낼수 있는 operator가 있습니다.

1
2
3
4
5
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .filter(title -> title != null)
    .subscribe(title -> System.out.println(title));
cs


filter() 은 부울 체크가 된 아이템에 대해서만 방출합니다.


다음으로 오직 5개의 결과만 보여주고 싶습니다.

이 경우에는 다음과 같이 작성하면 됩니다.


1
2
3
4
5
6
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .filter(title -> title != null)
    .take(5)
    .subscribe(title -> System.out.println(title));
cs



각각의 Title에 대해 디스크에 저장하고 싶은 경우


1
2
3
4
5
6
7
query("Hello, world!")
    .flatMap(urls -> Observable.from(urls))
    .flatMap(url -> getTitle(url))
    .filter(title -> title != null)
    .take(5)
    .doOnNext(title -> saveTitle(title))
    .subscribe(title -> System.out.println(title));
cs

doOnNext() 은 항목이 방출 될 때마다 추가 동작을 추가 할 수 있습니다.



Key idea #3: Operator는 데이터 스트림에 대해 무엇이든 할 수 있다.

'Language > Java' 카테고리의 다른 글

JVM 구조  (0) 2019.03.07
RxJava란? -4 Scheduler  (0) 2019.01.26
RxJava란 - 2 Operators  (0) 2019.01.26
RxJava란? - 기본 구조  (5) 2019.01.26
JDBC- MariaDB와 Java연동  (0) 2019.01.02