Khi nào bạn sử dụng map vs FlatMap trong RxJava?


180

Khi nào bạn sử dụng mapvs flatMaptrong RxJava ?

Ví dụ, giả sử, chúng tôi muốn ánh xạ các tệp có chứa JSON thành các chuỗi có chứa JSON--

Sử dụng map, chúng ta phải đối phó với một số Exceptioncách. Nhưng bằng cách nào?:

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 :(
    }
});

Sử dụng flatMap, nó dài dòng hơn nhiều, nhưng chúng ta có thể chuyển vấn đề xuống chuỗi Observablesvà xử lý lỗi nếu chúng ta chọn một nơi khác và thậm chí thử lại:

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);
                }
            }
        });
    }
});

Tôi thích sự đơn giản của map, nhưng việc xử lý lỗi flatmap(không phải tính dài dòng). Tôi chưa thấy bất kỳ thực hành tốt nhất nào về việc này trôi nổi xung quanh và tôi tò mò về cách sử dụng nó trong thực tế.

Câu trả lời:


121

mapbiến đổi sự kiện này sang sự kiện khác. flatMapbiến đổi một sự kiện thành không hoặc nhiều sự kiện. (cái này được lấy từ IntroToRx )

Khi bạn muốn biến json của mình thành một đối tượng, sử dụng bản đồ là đủ.

Đối phó với FileNotFoundException là một vấn đề khác (sử dụng bản đồ hoặc sơ đồ phẳng sẽ không giải quyết được vấn đề này).

Để giải quyết vấn đề Ngoại lệ của bạn, chỉ cần ném nó với một ngoại lệ Không được kiểm tra: RX sẽ gọi trình xử lý onError cho bạn.

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);
        }
    }
});

phiên bản chính xác tương tự với bản đồ phẳng:

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
        }
    }
});

Bạn cũng có thể quay lại, trong phiên bản FlatMap, một Observable mới chỉ là một lỗi.

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
Điều này không gọi, subscriber.onError()vv Tất cả các ví dụ tôi đã thấy có lỗi định tuyến theo cách đó. Điều đó không quan trọng?
Christopher Perry

7
Lưu ý rằng các hàm tạo của OnErrorThrowableprivatevà bạn cần sử dụng OnErrorThrowable.from(e)thay thế.
david.mihola

Tôi vừa cập nhật. OnErrorThrowable.from (e) không giữ giá trị, vì vậy tôi sử dụng OnErrorThrowable.addValueAsLastCause (e, tệp) để giữ giá trị.
dwursteisen

1
Tôi thích các ví dụ mã, nhưng sẽ hữu ích nếu bạn cập nhật chữ ký của các lệnh gọi FlatMap để trả về <Chuỗi> có thể quan sát thay vì chỉ Chuỗi ... bởi vì về mặt kỹ thuật có phải là sự khác biệt giữa hai không?
Rich Ehmer

78

FlatMap hoạt động rất giống bản đồ, điểm khác biệt là chức năng mà nó áp dụng trả về chính bản thân nó có thể quan sát được, do đó, nó hoàn toàn phù hợp để ánh xạ qua các hoạt động không đồng bộ.

Theo nghĩa thực tế, chức năng Bản đồ được áp dụng chỉ thực hiện chuyển đổi qua phản hồi chuỗi (không trả về một Quan sát); trong khi hàm FlatMap áp dụng trả về một Observable<T>, đó là lý do tại sao FlatMap được khuyến nghị nếu bạn có kế hoạch thực hiện cuộc gọi không đồng bộ bên trong phương thức.

Tóm lược:

  • Bản đồ trả về một đối tượng loại T
  • FlatMap trả về một quan sát.

Một ví dụ rõ ràng có thể được nhìn thấy ở đây: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk .

Couchbase Java 2.X Client sử dụng Rx để cung cấp các cuộc gọi không đồng bộ một cách thuận tiện. Vì nó sử dụng Rx, nó có sơ đồ phương thức và FlatMap, nên lời giải thích trong tài liệu của họ có thể hữu ích để hiểu khái niệm chung.

Để xử lý lỗi, ghi đè lênError trên người đăng ký của bạn.

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) { }
};

Có thể giúp xem tài liệu này: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Bạn có thể tìm thấy một nguồn tốt về cách quản lý lỗi với RX tại: https://gist.github.com/daschl/db9fcc9d2b932115b679


Tóm tắt là sai. Map và FlatMap trả về cùng loại nhưng hàm họ áp dụng trả về loại khác.
CoXier

61

Trong trường hợp của bạn, bạn cần bản đồ, vì chỉ có 1 đầu vào và 1 đầu ra.

chức năng được cung cấp bản đồ chỉ đơn giản là chấp nhận một mục và trả về một mục sẽ được phát ra thêm (chỉ một lần).

FlatMap - chức năng được cung cấp chấp nhận một mục sau đó trả về "Có thể quan sát", nghĩa là mỗi mục của "Có thể quan sát" mới sẽ được phát ra riêng biệt hơn nữa.

Có thể mã sẽ làm rõ mọi thứ cho bạn:

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);

Đầu ra:

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

Không chắc chắn nếu sử dụng bản đồ là ý tưởng tốt nhất, mặc dù nó sẽ hoạt động. Giả sử FileReader trở thành một cuộc gọi không đồng bộ. Sau đó, bạn cần thay đổi bản đồ thành bản đồ phẳng. Để nó dưới dạng bản đồ có nghĩa là bạn sẽ không nhận được các sự kiện như mong đợi và sẽ gây nhầm lẫn. Tôi đã bị cắn bởi điều này một vài lần khi tôi vẫn đang học RX Java. Tôi thấy FlatMap là một cách chắc chắn để đảm bảo mọi thứ được xử lý như bạn mong đợi.
dùng924272

24

Cách tôi nghĩ về nó là bạn sử dụng flatMapkhi hàm bạn muốn đặt bên trong map()trả về một Observable. Trong trường hợp bạn vẫn có thể cố gắng sử dụng map()nhưng nó sẽ không thực tế. Hãy để tôi cố gắng giải thích tại sao.

Nếu trong trường hợp như vậy bạn quyết định gắn bó map, bạn sẽ nhận được một Observable<Observable<Something>>. Ví dụ: trong trường hợp của bạn, nếu chúng tôi sử dụng thư viện RxGson tưởng tượng, đã trả về một phương thức Observable<String>từ nó toJson()(thay vì chỉ trả về a String), nó sẽ trông như thế này:

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

Tại thời điểm này, nó sẽ khá khó khăn để subscribe()có thể quan sát như vậy. Bên trong nó, bạn sẽ nhận được một Observable<String>cái mà bạn sẽ cần subscribe()để nhận được giá trị. Đó là không thực tế hoặc tốt đẹp để xem xét.

Vì vậy, để làm cho nó hữu ích, một ý tưởng là "làm phẳng" điều có thể quan sát được này (bạn có thể bắt đầu xem tên _flat_Map đến từ đâu). RxJava cung cấp một vài cách để làm phẳng các vật thể quan sát và vì đơn giản, hãy cho rằng hợp nhất là những gì chúng ta muốn. Hợp nhất về cơ bản có một loạt các vật quan sát và phát ra bất cứ khi nào chúng phát ra. (Rất nhiều người sẽ cho rằng chuyển đổi sẽ là một mặc định tốt hơn. Nhưng nếu bạn chỉ phát ra một giá trị, thì dù sao đi nữa cũng không thành vấn đề.)

Vì vậy, sửa đổi đoạn trích trước của chúng tôi, chúng tôi sẽ nhận được:

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

Điều này hữu ích hơn rất nhiều, vì đăng ký vào đó (hoặc ánh xạ hoặc lọc, hoặc ...) bạn chỉ nhận được Stringgiá trị. (Ngoài ra, hãy nhớ rằng, biến thể như vậy merge()không tồn tại trong RxJava, nhưng nếu bạn hiểu ý tưởng hợp nhất thì tôi hy vọng bạn cũng hiểu cách thức hoạt động của nó.)

Vì vậy, về cơ bản bởi vì điều đó merge()có lẽ chỉ hữu ích khi nó thành công map()trở lại có thể quan sát được và do đó bạn không phải gõ lại nhiều lần, flatMap()được tạo ra như một cách viết tắt. Nó áp dụng chức năng ánh xạ như bình thường map(), nhưng sau đó thay vì phát ra các giá trị được trả về, nó cũng "làm phẳng" (hoặc hợp nhất) chúng.

Đó là trường hợp sử dụng chung. Nó hữu ích nhất trong một cơ sở mã sử dụng Rx allover vị trí và bạn đã có nhiều phương thức trả về các vật quan sát, mà bạn muốn xâu chuỗi với các phương thức khác trả về các vật quan sát.

Trong trường hợp sử dụng của bạn, nó cũng có ích, bởi vì map()chỉ có thể chuyển đổi một giá trị được phát ra onNext()thành một giá trị khác được phát ra onNext(). Nhưng nó không thể chuyển đổi nó thành nhiều giá trị, không có giá trị nào hoặc có lỗi. Và như akarnokd đã viết trong câu trả lời của anh ấy (và nhớ rằng anh ấy thông minh hơn tôi rất nhiều, có lẽ nói chung, nhưng ít nhất là khi nói đến RxJava), bạn không nên ném ngoại lệ từ bạn map(). Vì vậy, thay vào đó bạn có thể sử dụng flatMap()

return Observable.just(value);

khi mọi việc suôn sẻ, nhưng

return Observable.error(exception);

khi một cái gì đó thất bại.
Xem câu trả lời của anh ấy cho một đoạn hoàn chỉnh: https://stackoverflow.com/a/30330772/1402641


1
đây là câu trả lời ưa thích của tôi về cơ bản, bạn kết thúc việc lồng một cái có thể quan sát vào một IF có thể quan sát được, đó là những gì phương thức của bạn trả về.
filthy_wizard

21

Câu hỏi là khi nào bạn sử dụng map vs FlatMap trong RxJava? . Và tôi nghĩ một bản demo đơn giản là cụ thể hơn.

Khi bạn muốn chuyển đổi mục được phát sang loại khác, trong trường hợp của bạn, chuyển đổi tệp thành Chuỗi, bản đồ và bản đồ phẳng có thể hoạt động. Nhưng tôi thích điều hành bản đồ hơn vì nó rõ ràng hơn.

Tuy nhiên, ở một số nơi, flatMapcó thể làm công việc ma thuật nhưng mapkhông thể. Ví dụ: tôi muốn lấy thông tin của người dùng nhưng trước tiên tôi phải lấy id của anh ấy khi người dùng đăng nhập. Rõ ràng tôi cần hai yêu cầu và chúng được xếp theo thứ tự.

Hãy bắt đầu nào.

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

Observable<UserInfo> fetchUserInfo(String userId);

Đây là hai phương thức, một cho đăng nhập được trả về Responsevà một phương pháp để tìm nạp thông tin người dùng.

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

Như bạn thấy, trong chức năng FlatMap áp dụng, đầu tiên tôi nhận id người dùng từ Responseđó sau đó tìm nạp thông tin người dùng. Khi hai yêu cầu kết thúc, chúng ta có thể thực hiện công việc của mình như cập nhật UI hoặc lưu dữ liệu vào cơ sở dữ liệu.

Tuy nhiên nếu bạn sử dụng, mapbạn không thể viết mã đẹp như vậy. Trong một từ, flatMapcó thể giúp chúng tôi tuần tự hóa các yêu cầu.


18

Đây là một đơn giản ngón tay cái quy tắc mà tôi sử dụng giúp tôi quyết định như khi sử dụng flatMap()trên map()trong Rx của Observable.

Khi bạn đi đến quyết định rằng bạn sẽ sử dụng một mapchuyển đổi, bạn sẽ viết mã chuyển đổi của mình để trả về một số Đối tượng phải không?

Nếu những gì bạn đang trở lại là kết quả cuối cùng của chuyển đổi của bạn là:

  • một đối tượng không thể quan sát sau đó bạn sẽ sử dụng chỉmap() . Và map()kết thúc đối tượng đó trong một Đài quan sát và phát ra nó.

  • một Observableđối tượng, sau đó bạn sẽ sử dụngflatMap() . Và flatMap()mở khóa Observable, chọn đối tượng được trả lại, bọc nó bằng Observable của chính nó và phát ra nó.

Ví dụ, giả sử chúng ta có một phương thức titleCase (String inputParam) trả về đối tượng Chuỗi có tiêu đề của tham số đầu vào. Kiểu trả về của phương thức này có thể là Stringhoặc Observable<String>.

  • Nếu kiểu trả về titleCase(..)chỉ là String, thì bạn sẽ sử dụngmap(s -> titleCase(s))

  • Nếu kiểu trả về titleCase(..)Observable<String>, thì bạn sẽ sử dụngflatMap(s -> titleCase(s))

Mong rằng làm rõ.


11

Tôi chỉ muốn thêm rằng flatMap, bạn không thực sự cần phải sử dụng Quan sát tùy chỉnh của riêng mình bên trong chức năng và bạn có thể dựa vào các phương thức / toán tử tiêu chuẩn của nhà máy:

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);
        }
    }
});

Nói chung, bạn nên tránh ném ngoại lệ (Runtime-) từ các phương thức onXXX và gọi lại nếu có thể, mặc dù chúng tôi đã đặt nhiều biện pháp bảo vệ nhất có thể trong RxJava.


Nhưng tôi nghĩ bản đồ là đủ. Vì vậy, FlatMap và bản đồ là một thói quen phải không?
CoXier

6

Trong kịch bản sử dụng bản đồ đó, bạn không cần một Đài quan sát mới cho nó.

bạn nên sử dụng Exceptions.propagate, đây là một trình bao bọc để bạn có thể gửi các ngoại lệ được kiểm tra đó đến cơ chế 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
        } 
    } 
});

Sau đó, bạn nên xử lý lỗi này trong thuê bao

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 }
};); 

Có một bài viết tuyệt vời cho nó: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/


0

Trong một số trường hợp, bạn có thể có chuỗi quan sát, trong đó khả năng quan sát của bạn sẽ trả lại một thứ có thể quan sát được. 'Flatmap' loại bỏ các thứ có thể quan sát thứ hai được chôn trong cái thứ nhất và cho phép bạn truy cập trực tiếp vào dữ liệu thứ hai có thể quan sát được phun ra trong khi đăng ký.


0

Bản đồ phẳng có thể quan sát để quan sát. Bản đồ các mục để các mục.

Flatmap linh hoạt hơn nhưng Map nhẹ hơn và trực tiếp hơn, do đó, loại này phụ thuộc vào usecase của bạn.

Nếu bạn đang thực hiện BẤT K as async (bao gồm cả chuyển đổi chủ đề), bạn nên sử dụng Flatmap, vì Map sẽ không kiểm tra xem người tiêu dùng có bị loại bỏ hay không (một phần của tính năng nhẹ)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.