簡體   English   中英

你什么時候在 RxJava 中使用 map 和 flatMap?

[英]When do you use map vs flatMap in RxJava?

你什么時候在RxJava 中使用mapflatMap

舉例來說,我們想將包含 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的錯誤處理(不是冗長)。 我還沒有看到任何關於此的最佳實踐,我很好奇它在實踐中是如何使用的。

map將一個事件轉換為另一個事件。 flatMap將一個事件轉換為零個或多個事件。 (這是取自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));
        }
    }
});

FlatMap 的行為與 map 非常相似,不同之處在於它應用的函數本身返回一個 observable,因此它非常適合映射異步操作。

在實際意義上,函數 Map 應用只是對鏈式響應進行轉換(不返回 Observable); 雖然函數 FlatMap 應用返回一個Observable<T> ,這就是為什么如果您打算在方法內進行異步調用,建議使用 FlatMap 。

概括:

  • Map 返回一個類型為 T 的對象
  • FlatMap 返回一個 Observable。

一個明顯的例子可以在這里看到: http : //blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk

Couchbase Java 2.X Client 使用 Rx 以方便的方式提供異步調用。 由於它使用 Rx,它有方法 map 和 FlatMap,他們文檔中的解釋可能有助於理解一般概念。

要處理錯誤,請覆蓋訂閱者的 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

在您的情況下,您需要地圖,因為只有 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+++

問題是你什么時候在 RxJava 中使用 map 和 flatMap? . 我認為一個簡單的演示更具體。

當您想將發出的 item 轉換為另一種類型時,在您的情況下,將文件轉換為 String,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 函數中,首先我從Response獲取用戶 ID,然后獲取用戶信息。 當兩個請求完成后,我們可以完成我們的工作,例如更新 UI 或將數據保存到數據庫中。

但是,如果您使用map ,則無法編寫如此出色的代碼。 總之, flatMap可以幫助我們序列化請求。

我的想法是,當您想放入map()的函數返回Observable時,您可以使用flatMap 在這種情況下,您可能仍會嘗試使用map()但這是不切實際的。 讓我試着解釋一下原因。

如果在這種情況下你決定堅持使用map ,你會得到一個Observable<Observable<Something>> 例如,在你的情況下,如果我們使用一個虛構的 RxGson 庫,它從它的toJson()方法返回一個Observable<String> (而不是簡單地返回一個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 會非常棘手。 在其中,您將獲得一個Observable<String> ,您將再次需要subscribe()以獲取該值。 這不實用或不好看。

因此,為了使其有用,一個想法是“扁平化”這個可觀察的可觀察對象(您可能會開始看到名稱 _flat_Map 的來源)。 RxJava 提供了幾種扁平化 observable 的方法,為了簡單起見,我們假設合並是我們想要的。 Merge 基本上需要一堆 observables 並在其中任何一個發出時發出。 (很多人會認為switch是一個更好的默認值。但如果你只發出一個值,無論如何都無所謂。)

所以修改我們之前的代碼片段,我們會得到:

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值。 (另外,請注意,RxJava 中不存在這種merge()變體,但是如果您了解合並的想法,那么我希望您也了解它是如何工作的。)

所以基本上因為這樣的merge()可能只在成功map()返回一個可觀察對象時才有用,所以你不必一遍又一遍地輸入, flatMap()被創建為速記。 它像普通的map()一樣應用映射函數,但稍后它不會發出返回的值,而是“扁平化”(或合並)它們。

這是一般用例。 它在到處使用 Rx 的代碼庫中最有用,並且您有許多返回 observable 的方法,您希望將這些方法與返回 observable 的其他方法鏈接起來。

在您的使用情況下,它碰巧是也很有用,因為map()只能變換發出一個值onNext()到發射的另一種價值onNext() 但它不能將其轉換為多個值,根本沒有值或錯誤。 正如akarnokd在他的回答中所寫(請注意,他比我聰明得多,可能在一般情況下,但至少在涉及 RxJava 時)您不應該從map()拋出異常。 因此,您可以使用flatMap()

return Observable.just(value);

當一切順利時,但是

return Observable.error(exception);

當某事失敗時。
請參閱他對完整片段的回答: https : //stackoverflow.com/a/30330772/1402641

這是一個簡單的經驗法則,我使用它幫助我決定何時在 Rx 的Observable使用flatMap()不是map()

一旦您決定要使用map轉換,您就會編寫轉換代碼來返回一些對象,對嗎?

如果您作為轉換的最終結果返回的是:

  • 一個不可觀察的對象,那么您只需使用map() map()將該對象包裝在一個 Observable 中並發出它。

  • 一個Observable對象,然后你會使用flatMap() flatMap()解開 Observable,選擇返回的對象,用它自己的 Observable 包裝它並發出它。

例如,我們有一個方法 titleCase(String inputParam) 返回輸入參數的 Titled Cased String 對象。 此方法的返回類型可以是StringObservable<String>

  • 如果titleCase(..)的返回類型僅僅是String ,那么您將使用map(s -> titleCase(s))

  • 如果titleCase(..)的返回類型是Observable<String> ,那么您將使用flatMap(s -> titleCase(s))

希望澄清。

我只是想用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);
        }
    }
});

通常,您應該盡可能避免從 onXXX 方法和回調中拋出(運行時)異常,即使我們在 RxJava 中放置了盡可能多的保護措施。

在那種情況下使用地圖,你不需要一個新的 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/

在某些情況下,您最終可能會擁有一連串的 observable,其中您的 observable 會返回另一個 observable。 'flatmap' 可以解開隱藏在第一個中的第二個 observable,讓您直接訪問第二個 observable 在訂閱時吐出的數據。

Flatmap 將 observables 映射到 observables。 地圖將項目映射到項目。

Flatmap 更靈活,但 Map 更輕量和直接,因此這取決於您的用例。

如果您正在執行任何異步操作(包括切換線程),您應該使用 Flatmap,因為 Map 不會檢查消費者是否已處理(輕量級的一部分)

RxJava Map 與 FlatMap

它們都是轉換運算符,但map具有 1-1 關系,而flatMap具有 1-0 或多個關系。

  • mapflatmap發出的只有 1 個元素用於map或 0/many 用於flatmap
  • map發出單個元素,而flatmap發出元素流

地圖運算符

map(new Function<A, B>() {
    @Override
    public User apply(A a) throws Exception {
        B b = new B(a);
        return b;
    }
})

FlatMap 運算符

flatMap(new Function<A, ObservableSource<B>>() { 
    @Override
    public ObservableSource<B> apply(A a) throws Exception {
        return foo(a);
    }
})

[flatMap 與 concatMap]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM