2014-04-03 17 views
129

Khi nào bạn sử dụng bản đồ vs flatMap trong RxJava?Khi nào bạn sử dụng bản đồ vs flatMap trong RxJava?

Nói ví dụ, chúng tôi muốn để ánh xạ tập tin chứa JSON vào Strings chứa JSON--

Sử dụng bản đồ, chúng ta phải đối phó với những ngoại lệ nào đó. Nhưng làm thế 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ó là nhiều hơn nữa tiết, nhưng chúng tôi có thể chuyển tiếp các vấn đề xuống chuỗi quan sát và xử lý các 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 bản đồ, nhưng việc xử lý lỗi của sơ đồ phẳng (không phải là độ dài). Tôi đã không nhìn thấy bất kỳ thực hành tốt nhất trên này nổi xung quanh và tôi tò mò làm thế nào điều này đang được sử dụng trong thực tế.

Trả lời

88

map chuyển đổi sự kiện này sang sự kiện khác. flatMap chuyển đổi một sự kiện thành 0 hoặc nhiều sự kiện. (điều này được lấy từ IntroToRx)

Khi bạn muốn biến đổi json của bạn 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 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); 
     } 
    } 
}); 

cùng phiên bản chính xác với 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 
     } 
    } 
}); 

Bạn có thể trở lại quá, trong phiên bản flatMap một Quan sát 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 đã định tuyến lỗi theo cách đó. Điều đó có quan trọng không? –

+7

Lưu ý rằng các hàm tạo của 'OnErrorThrowable' là' private' và bạn cần sử dụng 'OnErrorThrowable.from (e)' để thay thế. –

+0

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, file) thay vào đó, nên giữ giá trị. – dwursteisen

53

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

Trong ý nghĩa thực tế, hàm Map áp dụng chỉ thực hiện một phép chuyển đổi qua đáp ứng chuỗi (không trả về một Observable); trong khi hàm FlatMap áp dụng trả về một Observable<T>, đó là lý do tại sao FlatMap được đề xuất 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 tắt:

  • Bản đồ trả về một đối tượng kiểu T
  • flatMap trả về một quan sát được.

Ví dụ rõ ràng có thể xem tại đây: http://blog.couchbase.com/why-couchbase-chose-rxjava-new-java-sdk.

Couchbase Java 2.X Khách hàng 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ó các phương thức map và FlatMap, giải thích trong tài liệu của họ có thể hữu ích khi hiểu khái niệm chung.

Để xử lý lỗi, ghi đè lênError trên susbcriber 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) { } 
}; 

Nó có thể giúp nhìn vào tài liệu này: http://blog.danlew.net/2014/09/15/grokking-rxjava-part-1/

Một nguồn tin tốt về cách quản lý lỗi với RX thể được tìm thấy tại địa chỉ: https://gist.github.com/daschl/db9fcc9d2b932115b679

+0

Tóm tắt là sai. Bản đồ và FlatMap trả về cùng một kiểu nhưng hàm chúng áp dụng kiểu trả về khác nhau. – CoXier

38

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

chức năng bản đồ được cung cấp chỉ đơn giản chấp nhận một mục và trả về một mục sẽ được phát ra thêm nữa (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 "Quan sát" mới sẽ được phát ra riêng biệt hơn nữa.

Có thể mã sẽ xóa mọi thứ cho bạn.

 //START DIFFERENCE BETWEEN MAP AND FLATMAP 
    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); 
    //END DIFFERENCE BETWEEN MAP AND FLATMAP 

Output:

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

Mã của bạn không chạy .. bạn có muốn viết 'Observable.just (" item1 ")'? –

+1

Cảm ơn, Henrique de Sousa –

10

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

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ừ phương phápXXX 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ư chúng tôi có thể ở RxJava.

+0

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

4

Trong trường hợp đó 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, mà là một wrapper, do đó bạn có thể gửi những trường hợp ngoại lệ kiểm tra để 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 các 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ó là một bài tuyệt vời cho nó: http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

20

Cách tôi nghĩ về nó là bạn sử dụng flatMap khi hàm bạn muốn đặt bên trong số map() trả về số Observable. Trong trường hợp đó, bạn vẫn có thể thử sử dụng map() nhưng 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 này, bạn quyết định gắn bó với map, bạn sẽ nhận được Observable<Observable<Something>>. Ví dụ trong trường hợp của bạn, nếu chúng ta sử dụng một thư viện RxGson tưởng tượng, đó quay trở lại một Observable<String> từ nó toJson() phương pháp (thay vì chỉ đơn giản là trả lại một 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ẽ là khá khéo léo để subscribe() đến một quan sát như vậy. Bên trong nó, bạn sẽ nhận được một Observable<String> mà bạn sẽ lại cần phải subscribe() để lấy 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 này quan sát được các quan sát (bạn có thể bắt đầu thấy tên _flat_Map đến từ đâu). RxJava cung cấp một vài cách để làm phẳng các quan sát và vì mục đích đơn giản cho phép giả sử merge là những gì chúng ta muốn. Hợp nhất về cơ bản có một loạt các quan sát và phát ra bất cứ khi nào bất kỳ trong số họ phát ra. (Rất nhiều người sẽ cho switch sẽ là một mặc định tốt hơn Nhưng nếu bạn đang phát ra chỉ là một giá trị, nó không quan trọng anyway..)

Vì vậy, việc sửa đổi đoạn trước, 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, bởi vì đăng ký vào đó (hoặc ánh xạ, hoặc lọc, hoặc ...) bạn chỉ nhận được giá trị String. (Ngoài ra, nhớ bạn, biến thể như vậy của 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 đó sẽ hoạt động.)

Vì vậy, về cơ bản vì vậy merge() có lẽ chỉ bao giờ hữu ích khi nó thành công một map() trả lại một 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 hàm ánh xạ giống như bình thường map(), nhưng sau đó thay vì phát ra các giá trị 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 dụng nhất trong một codebase sử dụng Rx allover nơi và bạn có nhiều phương thức trả về các quan sát, mà bạn muốn kết nối với các phương thức khác trả về các quan sát.

Trong trường hợp sử dụng của bạn, điều này cũng hữu ích vì map() chỉ có thể chuyển đổi một giá trị được phát ra trong onNext() thành giá trị khác được phát ra trong onNext(). Nhưng nó không thể biến đổi nó thành nhiều giá trị, không có giá trị nào cả hoặc một lỗi. Và như akarnokd đã viết trong câu trả lời của mình (và nhớ bạn thông minh hơn tôi 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ừ map() của mình. 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 mình 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 sẽ làm tổ trong một IF quan sát được là phương thức của bạn trả về. –

10

Đâ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() qua map() trong Rx của Observable.

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

Nếu bạn đang muốn trở về như kết quả cuối cùng của sự biến đổi của bạn là:

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

  • đối tượng Observable, sau đó bạn muốn sử dụng flatMap(). Và flatMap() unwraps the Observable, chọn đối tượng trả về, kết thúc tốt đẹp nó với quan sát riêng của nó và phát ra nó.

Ví dụ: chúng tôi có 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à String hoặc Observable<String>.

  • Nếu kiểu trả về của titleCase(..) đã được chỉ String, sau đó bạn muốn sử dụng map(s -> titleCase(s))

  • Nếu kiểu trả về của titleCase(..) đã được Observable<String>, sau đó bạn muốn sử dụng flatMap(s -> titleCase(s))

Hy vọng làm rõ.

0

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

2

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

Khi bạn muốn chuyển đổi mục phát ra 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à flatMap đều có thể hoạt động. Nhưng tôi thích điều hành bản đồ vì nó rõ ràng hơn.

Tuy nhiên ở một số nơi, flatMap có thể thực hiện công việc ma thuật nhưng map không thể. Ví dụ, tôi muốn nhận được thông tin của người dùng nhưng trước tiên tôi phải có được id của mình khi người dùng đăng nhập. Rõ ràng tôi cần hai yêu cầu và họ đang theo thứ tự.

Hãy bắt đầu.

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

Observable<UserInfo> fetchUserInfo(String userId); 

Dưới đây là hai phương pháp, một phương thức cho đăng nhập được trả lại Response và một phương thức khác để 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 hàm flatMap áp dụng, lúc đầu tôi nhận id người dùng từ Response rồi tìm nạp thông tin người dùng. Khi hai yêu cầu được hoàn thành, chúng tôi có thể thực hiện công việc của mình như cập nhật giao diện người dùng hoặc lưu dữ liệu vào cơ sở dữ liệu.

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

Các vấn đề liên quan