【问题标题】:When do you use map vs flatMap in RxJava?你什么时候在 RxJava 中使用 map 和 flatMap?
【发布时间】:2014-05-15 20:10:43
【问题描述】:

你什么时候在 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 的错误处理(而不是冗长)。我还没有看到任何关于这个浮动的最佳实践,我很好奇它是如何在实践中使用的。

【问题讨论】:

    标签: java mapping rx-java flatmap


    【解决方案1】:

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

    与平面图完全相同的版本:

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

    【讨论】:

    • 这不会调用subscriber.onError() 等。我看到的所有示例都以这种方式路由错误。没关系吗?
    • 注意OnErrorThrowable的构造函数是private,需要用OnErrorThrowable.from(e)代替。
    • 我刚刚更新了。 OnErrorThrowable.from(e) 不保留该值,因此我使用 OnErrorThrowable.addValueAsLastCause(e, file) 代替,它应该保留该值。
    • 我喜欢这些代码示例,但如果您更新 flatMap 调用的签名以返回 Observable 而不仅仅是 String ...因为从技术上讲这不是两个?
    【解决方案2】:

    FlatMap 的行为与 map 非常相似,不同之处在于 它应用的函数返回一个 observable 本身,因此它非常适合映射异步操作。

    在实际意义上,Map 应用的函数只是对链式响应进行转换(不返回 Observable);而 FlatMap 应用的函数返回一个Observable&lt;T&gt;,这就是为什么如果您计划在方法内进行异步调用,建议使用 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

    【讨论】:

    • 总结有误。 Map 和 FlatMap 返回相同的类型,但它们应用的函数返回不同的类型。
    【解决方案3】:

    在您的情况下,您需要地图,因为只有 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 将成为一个异步调用。然后您需要将地图更改为平面地图。将其保留为地图意味着您不会按预期触发事件,并且会引起混乱。因为我还在学习 RX Java,所以我已经被这个咬过几次了。我发现 flatMap 是确保事情按预期处理的可靠方法。
    【解决方案4】:

    问题是你什么时候在 RxJava 中使用 map 和 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 函数中,首先我从 Response 获取用户 ID,然后获取用户信息。当两个请求完成后,我们就可以完成我们的工作,例如更新 UI 或将数据保存到数据库中。

    但是,如果你使用map,你就写不出这么好的代码。总之flatMap可以帮我们序列化请求。

    【讨论】:

      【解决方案5】:

      我的想法是,当您想放入map() 的函数返回Observable 时,您使用flatMap。在这种情况下,您可能仍会尝试使用map(),但这不切实际。让我试着解释一下原因。

      如果在这种情况下您决定坚持使用map,您将获得Observable&lt;Observable&lt;Something&gt;&gt;。例如,在您的情况下,如果我们使用一个虚构的 RxGson 库,它会从它的 toJson() 方法返回一个 Observable&lt;String&gt;(而不是简单地返回一个 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&lt;String&gt;,你需要再次subscribe()来获取值。这既不实用也不好看。

      因此,为了使它有用,一个想法是“扁平化”这个 observable 的 observable(你可能会开始看到 _flat_Map 这个名字的来历)。 RxJava 提供了一些扁平化 observables 的方法,为了简单起见,我们假设 merge 是我们想要的。 Merge 基本上需要一堆 observables 并在它们中的任何一个发出时发出。 (很多人会争辩 switch 会是更好的默认值。但如果你只发出一个值,那也没关系。)

      所以修改我们之前的 sn-p 我们会得到:

      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 的代码库中最有用,并且您有许多返回 observables 的方法,您希望将这些方法与返回 observables 的其他方法链接起来。

      在您的用例中,它恰好也很有用,因为map() 只能将onNext() 中发出的一个值转换为onNext() 中发出的另一个值。但它不能将其转换为多个值,根本没有值或错误。正如akarnokd 在他的回答中所写的那样(请注意,他可能比我聪明得多,可能在一般情况下,但至少在 RxJava 方面)你不应该从你的map() 中抛出异常。因此,您可以使用 flatMap()

      return Observable.just(value);
      

      一切顺利,但是

      return Observable.error(exception);
      

      当某事失败时。
      查看他完整的 sn-p 答案:https://stackoverflow.com/a/30330772/1402641

      【讨论】:

      • 这是我最喜欢的答案。你基本上最终会在你的方法返回的 observable IF 中嵌套一个 observable。
      【解决方案6】:

      这是一个简单的 thumb-rule,我使用它来帮助我决定何时在 Rx 的 Observable 中使用 flatMap() 而不是 map()

      一旦您决定使用map 转换,您会编写转换代码来返回一些对象,对吗?

      如果您作为转换的最终结果返回的是:

      • 一个不可观察的对象,那么你只需要使用map()。而map() 将该对象包装在一个 Observable 中并发出它。

      • Observable 对象,那么您将使用 flatMap()flatMap() 解开 Observable,挑选返回的对象,用自己的 Observable 包装并发出它。

      例如,我们有一个方法 titleCase(String inputParam),它返回输入参数的 Titled Cased String 对象。该方法的返回类型可以是StringObservable&lt;String&gt;

      • 如果titleCase(..) 的返回类型仅为String,那么您将使用map(s -&gt; titleCase(s))

      • 如果titleCase(..) 的返回类型为Observable&lt;String&gt;,那么您将使用flatMap(s -&gt; titleCase(s))

      希望澄清。

      【讨论】:

        【解决方案7】:

        我只是想在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 中设置了尽可能多的保护措施。

        【讨论】:

        • 但我觉得地图就够了。所以flatMap和map是一种习惯吧?
        【解决方案8】:

        在那种情况下使用地图,你不需要一个新的 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/

        【讨论】:

          【解决方案9】:

          RxJava 地图与平面地图

          它们都是转换运算符,但 map 具有 1-1 关系,flatMap 具有 1-0 或多个关系。

          • mapflatmap 发出 stream
            • map- 只有 1 个元素
            • flatmap - 0/许多元素
          • map 发出单个元素,flatmap 发出 元素

          地图运算符

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

          平面图运算符

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

          [flatMap vs concatMap]

          [Swift map vs flatMap]

          【讨论】:

            【解决方案10】:

            在某些情况下,您最终可能会拥有一系列可观察对象,其中您的可观察对象会返回另一个可观察对象。 'flatmap' 将第二个 observable 展开,它隐藏在第一个 observable 中,让您直接访问第二个 observable 在订阅时吐出的数据。

            【讨论】:

              【解决方案11】:

              Flatmap 将可观察对象映射到可观察对象。 Map 将项目映射到项目。

              Flatmap 更灵活,但 Map 更轻量级和直接,因此它取决于您的用例。

              如果你正在做任何异步操作(包括切换线程),你应该使用 Flatmap,因为 Map 不会检查消费者是否被释放(轻量级的一部分)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-05-21
                • 1970-01-01
                • 1970-01-01
                • 2015-03-26
                • 2010-09-19
                • 2012-04-23
                • 2018-01-17
                • 1970-01-01
                相关资源
                最近更新 更多