RxJava에서 언제 map vs flatMap을 사용합니까?


180

언제 RxJava 에서 mapvs 를 사용 합니까?flatMap

예를 들어 JSON이 포함 된 파일을 JSON이 포함 된 문자열에 매핑하려고합니다.

를 사용 map하여 Exception어떻게 든 처리해야합니다 . 그러나 어떻게? :

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // So Exception. What to do ?
        }
        return null; // Not good :(
    }
});

를 사용하면 flatMap훨씬 더 장황하지만 문제를 Observables다른 것으로 선택하고 오류를 처리하면 다시 시도 할 수 있습니다.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override public void call(Subscriber<? super String> subscriber) {
                try {
                    String json = new Gson().toJson(new FileReader(file), Object.class);

                    subscriber.onNext(json);
                    subscriber.onCompleted();
                } catch (FileNotFoundException e) {
                    subscriber.onError(e);
                }
            }
        });
    }
});

나는의 단순함을 좋아 map하지만 flatmap(자세한 것은 아닙니다)의 오류 처리 . 나는 떠 다니는 것에 대한 모범 사례를 보지 못했고 이것이 실제로 어떻게 사용되고 있는지 궁금합니다.

답변:


121

map한 이벤트를 다른 이벤트로 변환하십시오. flatMap하나의 이벤트를 0 개 이상의 이벤트로 변환합니다. (이것은 IntroToRx 에서 가져온 것입니다 )

json을 객체로 변환하려면 map을 사용하는 것으로 충분합니다.

FileNotFoundException을 다루는 것은 또 다른 문제입니다 (map 또는 flatmap을 사용하면이 문제가 해결되지 않습니다).

예외 문제를 해결하려면 확인되지 않은 예외로 처리하십시오. RX가 onError 핸들러를 호출합니다.

Observable.from(jsonFile).map(new Func1<File, String>() {
    @Override public String call(File file) {
        try {
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            // this exception is a part of rx-java
            throw OnErrorThrowable.addValueAsLastCause(e, file);
        }
    }
});

flatmap과 정확히 동일한 버전 :

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            // this static method is a part of rx-java. It will return an exception which is associated to the value.
            throw OnErrorThrowable.addValueAsLastCause(e, file);
            // alternatively, you can return Obersable.empty(); instead of throwing exception
        }
    }
});

flatMap 버전에서는 오류 인 새로운 Observable을 반환 할 수도 있습니다.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(File file) {
        try {
            return Observable.just(new Gson().toJson(new FileReader(file), Object.class));
        } catch (FileNotFoundException e) {
            return Observable.error(OnErrorThrowable.addValueAsLastCause(e, file));
        }
    }
});

2
이것은 subscriber.onError()등을 호출하지 않습니다 . 내가 본 모든 예제는 그런 식으로 오류를 라우팅했습니다. 중요하지 않습니까?
크리스토퍼 페리

7
의 생성자가 있습니다 OnErrorThrowable있습니다 private당신이 사용할 필요가 OnErrorThrowable.from(e)대신.
david.mihola

방금 업데이트했습니다. OnErrorThrowable.from (e)는 값을 유지하지 않으므로 대신 OnErrorThrowable.addValueAsLastCause (e, file)를 사용하여 값을 유지해야합니다.
dwursteisen

1
코드 예제가 마음에 들지만 flatMap 호출의 서명을 업데이트하여 String 대신 Observable <String>을 반환하면 도움이 될 것입니다 ... 기술적으로 두 가지의 차이점이 아니기 때문에?
Rich Ehmer 2016

78

FlatMap은 맵과 매우 유사하게 작동하지만 차이점은 적용하는 함수 가 관찰 가능 자체를 리턴하므로 비동기 작업에 대한 맵에 완벽하게 적합하다는 것입니다.

실용적인 의미에서 Map Apply 함수는 연결된 응답을 통해 변환을 수행합니다 (Observable을 반환하지 않음). FlatMap apply 함수는을 반환하지만 Observable<T>, 메소드 내에서 비동기 호출을 수행하려는 경우 FlatMap이 권장됩니다.

요약:

  • 지도는 T 유형의 객체를 반환합니다
  • FlatMap은 Observable을 반환합니다.

http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk 에서 명확한 예를 볼 수 있습니다 .

Couchbase Java 2.X Client는 Rx를 사용하여 편리한 방식으로 비동기 호출을 제공합니다. Rx를 사용하기 때문에 메소드 맵과 FlatMap이 있으며, 문서의 설명은 일반적인 개념을 이해하는 데 도움이 될 수 있습니다.

오류를 처리하려면 susbcriber에서 onError를 대체하십시오.

Subscriber<String> mySubscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) { System.out.println(s); }

    @Override
    public void onCompleted() { }

    @Override
    public void onError(Throwable e) { }
};

이 문서를 보는 것이 도움이 될 수 있습니다 : http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

RX의 오류를 관리하는 방법에 대한 좋은 소스는 https://gist.github.com/daschl/db9fcc9d2b932115b679 에서 찾을 수 있습니다.


요약이 잘못되었습니다. Map과 FlatMap은 같은 유형을 반환하지만 적용되는 함수는 다른 유형을 반환합니다.
CoXier

61

귀하의 경우 1 개의 입력과 1 개의 출력 만 있기 때문에 맵이 필요합니다.

map-제공된 함수는 단순히 항목을 받아들이고 한 번만 방출되는 항목을 반환합니다.

flatMap-제공된 함수는 항목을 승인 한 다음 "Observable"을 리턴합니다. 이는 새 "Observable"의 각 항목이 별도로 더 아래로 방출됨을 의미합니다.

코드가 당신을 위해 일을 정리 할 수 ​​있습니다 :

Observable.just("item1").map( str -> {
    System.out.println("inside the map " + str);
    return str;
}).subscribe(System.out::println);

Observable.just("item2").flatMap( str -> {
    System.out.println("inside the flatMap " + str);
    return Observable.just(str + "+", str + "++" , str + "+++");
}).subscribe(System.out::println);

산출:

inside the map item1
item1
inside the flatMap item2
item2+
item2++
item2+++

작동하지만지도를 사용하는 것이 가장 좋은 아이디어인지 확실하지 않습니다. FileReader가 비동기 호출이되었다고 가정하십시오. 그런 다음지도를 flatMap으로 변경해야합니다. 맵으로 남겨두면 예상대로 이벤트가 발생하지 않으며 혼란을 초래할 수 있습니다. 여전히 RX Java를 배우면서 몇 번이나 물 렸습니다. flatMap은 예상대로 작업을 처리하는 확실한 방법입니다.
user924272

24

내가 생각하는 방식 flatMap은 내부에 넣고 싶은 함수가를 map()반환 할 때 사용한다는 것 Observable입니다. 이 경우에도 여전히 사용하려고 map()하지만 실용적이지 않습니다. 이유를 설명하려고 노력하겠습니다.

그러한 경우에 고집하기로 결정 map했다면 Observable<Observable<Something>>. 예를 들어, 가상의 RxGson 라이브러리를 사용했다면 (그냥 단순히 반환하는 대신) 메소드 Observable<String>에서 를 반환하면 다음과 같습니다.toJson()String

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}); // you get Observable<Observable<String>> here

이 시점 subscribe()에서 그러한 관찰 가능한 것은 까다로울 것 입니다. 그것 안에 Observable<String>당신은 다시 subscribe()가치를 얻는 데 필요한 것을 얻을 것입니다. 실용적이지 않고보기에 좋지 않습니다.

따라서 유용한 한 가지 아이디어는이 관측 가능한 관측 가능 항목을 "평평하게"하는 것입니다 (이름이 _flat_Map 인 곳을 볼 수 있습니다). RxJava는 옵저버 블을 평탄화하는 몇 가지 방법을 제공하며 단순성을 위해 병합 이 원하는 것을 가정 수 있습니다 . 병합은 기본적으로 많은 관측 가능 항목을 가져 와서 방출 할 때마다 방출합니다. (많은 사람들은 스위치 가 더 나은 기본값 이라고 주장 것입니다. 그러나 하나의 값만 내 보내면 어쨌든 상관 없습니다.)

이전 스 니펫을 수정하면 다음과 같이됩니다.

Observable.from(jsonFile).map(new Func1<File, Observable<String>>() {
    @Override public Observable<String>> call(File file) {
        return new RxGson().toJson(new FileReader(file), Object.class);
    }
}).merge(); // you get Observable<String> here

구독 (또는 맵핑 또는 필터링 또는 ...)을 구독하면 String값을 얻을 수 있으므로 훨씬 유용 합니다. (또한 merge()RxJava에는 이러한 변형이 존재하지 않지만 병합 개념을 이해하면 작동 방식을 이해하기를 바랍니다.)

따라서 기본적으로 merge()이것들은 아마도 map()관측 값 반환에 성공했을 때만 유용 하기 때문에 이것을 반복해서 입력 할 필요가 없으므로 flatMap()속기로 작성되었습니다. 매핑 함수를 일반적인 방식으로 적용 map()하지만 나중에 반환 된 값을 내보내는 대신 "평평하게"(또는 병합)합니다.

이것이 일반적인 사용 사례입니다. Rx를 어디에서나 사용하는 코드베이스에서 가장 유용하며 옵저버 블을 반환하는 많은 메서드가 있으며 옵저버 블을 반환하는 다른 메서드와 체인을 연결하려고합니다.

사용 된 경우에는 map()방출 된 하나의 값만에서 방출 된 onNext()다른 값으로 변환 할 수 있기 때문에 유용합니다 onNext(). 그러나 여러 값으로 변환 할 수 없으며 전혀 값이 없거나 오류가 없습니다. 그리고 akarnokd 가 그의 대답에 썼 듯이 (그리고 아마도 그는 일반적으로 나보다 훨씬 똑똑하지만 적어도 RxJava에 관해서는) 당신에게서 예외를 던져서는 안됩니다 map(). 그래서 그 대신 사용할 수있는 flatMap()

return Observable.just(value);

모든 것이 잘되면

return Observable.error(exception);

무언가가 실패했을 때.
자세한 내용은 https://stackoverflow.com/a/30330772/1402641에 대한 답변을 참조하십시오.


1
이것이 내가 가장 좋아하는 대답입니다. 기본적으로 메소드가 반환하는 옵저버 블 IF에 옵저버 블 IN을 중첩시킵니다.
filthy_wizard

21

문제는 RxJava에서 언제 map vs flatMap을 사용합니까? . 간단한 데모가 더 구체적이라고 생각합니다.

방출 된 항목을 다른 유형으로 변환하려는 경우 파일을 문자열로 변환하면 map 및 flatMap이 모두 작동 할 수 있습니다. 그러나 나는 더 명확하기 때문에지도 연산자를 선호합니다.

그러나 어떤 곳에서는 flatMap마술 일을 map할 수 있지만 할 수는 없습니다. 예를 들어, 사용자 정보를 얻고 싶지만 사용자가 로그인 할 때 먼저 ID를 가져와야합니다. 분명히 두 개의 요청이 필요하며 순서대로되어 있습니다.

의 시작하자.

Observable<LoginResponse> login(String email, String password);

Observable<UserInfo> fetchUserInfo(String userId);

다음은 두 가지 방법으로, 하나는 로그인을 반환 Response하고 다른 하나는 사용자 정보를 가져 오는 방법입니다.

login(email, password)
        .flatMap(response ->
                fetchUserInfo(response.id))
        .subscribe(userInfo -> {
            // get user info and you update ui now
        });

보시다시피 flatMap 함수가 적용되면 처음에는 사용자 ID를 가져온 Response다음 사용자 정보를 가져옵니다. 두 개의 요청이 완료되면 UI 업데이트 또는 데이터베이스에 데이터 저장과 같은 작업을 수행 할 수 있습니다.

그러나 사용 map하면 멋진 코드를 작성할 수 없습니다. 한마디로 flatMap요청을 직렬화하는 데 도움이 될 수 있습니다.


18

여기에 간단 엄지 손가락 규칙 나는 나를 사용하는 경우와 같이 결정하는 데 도움을 사용하는 것이 flatMap()이상 map()수신의에서가 Observable.

map변환 을 사용하기로 결정 했다면 변환 코드를 작성하여 객체를 올바르게 반환합니까?

변환의 최종 결과로 돌아 오는 것이 다음과 같은 경우 :

  • 관찰 할 수없는 객체를 사용하면 그냥을 사용map() 합니다. 그리고 map()그 객체를 Observable에 싸서 방출합니다.

  • Observable객체는, 당신은 사용하십시오flatMap() . 그리고 flatMap()Observable을 풀고, 반환 된 객체를 선택하고, 자신의 Observable로 감싸서 방출합니다.

예를 들어 input param의 Titled Cased String 객체를 반환하는 titleCase (String inputParam) 메소드가 있다고 가정 해보십시오. 이 메소드의 리턴 유형은 String또는 Observable<String>입니다.

  • 의 반환 유형 titleCase(..)이 단순한 String경우라면map(s -> titleCase(s))

  • 의 반환 형식이있는 경우 titleCase(..)로했다 Observable<String>, 당신은 사용하십시오flatMap(s -> titleCase(s))

그 희망은 명확합니다.


11

방금 추가하고 싶었습니다 flatMap. 함수 내에서 사용자 정의 Observable을 실제로 사용할 필요가 없으며 표준 팩토리 메소드 / 연산자를 사용할 수 있습니다.

Observable.from(jsonFile).flatMap(new Func1<File, Observable<String>>() {
    @Override public Observable<String> call(final File file) {
        try {
            String json = new Gson().toJson(new FileReader(file), Object.class);
            return Observable.just(json);
        } catch (FileNotFoundException ex) {
            return Observable.<String>error(ex);
        }
    }
});

일반적으로 RxJava에서 가능한 한 많은 수의 안전 장치를 배치 했더라도 가능한 경우 onXXX 메소드 및 콜백에서 (런타임-) 예외를 발생시키지 않아야합니다.


그러나 나는지도가 충분하다고 생각합니다. 그래서 flatMap과 map은 습관입니다.
CoXier

6

이 시나리오에서는 맵을 사용하기 위해 새로운 Observable이 필요하지 않습니다.

래퍼 인 Exceptions.propagate를 사용해야하므로 확인 된 예외를 rx 메커니즘으로 보낼 수 있습니다.

Observable<String> obs = Observable.from(jsonFile).map(new Func1<File, String>() { 
    @Override public String call(File file) {
        try { 
            return new Gson().toJson(new FileReader(file), Object.class);
        } catch (FileNotFoundException e) {
            throw Exceptions.propagate(t); /will propagate it as error
        } 
    } 
});

그런 다음 구독자에서이 오류를 처리해야합니다.

obs.subscribe(new Subscriber<String>() {
    @Override 
    public void onNext(String s) { //valid result }

    @Override 
    public void onCompleted() { } 

    @Override 
    public void onError(Throwable e) { //e might be the FileNotFoundException you got }
};); 

그것에 대한 훌륭한 게시물이 있습니다 : http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/


0

어떤 경우에는 관측 가능 체인이 생길 수 있으며,이 경우 관측 가능 개체는 다른 관측 가능 개체를 반환합니다. '플랫 맵'종류는 첫 번째에 묻힌 두 번째 관찰 가능을 풀고 구독하는 동안 두 번째 관찰 가능 데이터가 직접 액세스하도록합니다.


0

플랫 맵은 Observable을 Observable에 매핑합니다. 지도는 항목을 항목에 매핑합니다.

플랫 맵은 더 유연하지만 맵은 더 가볍고 직접적이므로 사용 사례에 따라 다릅니다.

전환 스레드를 포함한 모든 비동기 작업을 수행하는 경우 소비자가 폐기되었는지 (경량의 일부인지)지도에서 확인하지 않으므로 플랫 맵을 사용해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.