你什么时候在RxJava中使用map vs flatMap?

时间:2014-04-03 19:26:03

标签: java mapping rx-java flatmap

你什么时候在RxJava中使用map vs 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,它更加冗长,但我们可以将问题转发到Observable链中,如果我们选择其他地方甚至重试,我们就会处理错误:

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

我喜欢地图的简单性,但是flatmap的错误处理(不是详细程度)。我还没有看到任何关于这种情况的最佳实践,我很好奇这是如何在实践中使用的。

11 个答案:

答案 0 :(得分:114)

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

答案 1 :(得分:75)

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客户端使用Rx以方便的方式提供异步调用。由于它使用Rx,因此它具有方法map和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

答案 2 :(得分:53)

在您的情况下,您需要地图,因为只有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+++

答案 3 :(得分:23)

我想到的方法是,当您要放在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提供了几种展平可观察对象的方法,为了简单起见,我们假设merge是我们想要的。合并基本上会带来一堆可观察的,并且只要它们中的任何一个发出就会发出。 (很多人会认为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的想法,那么我希望你也能理解它是如何工作的。)

所以基本上因为merge()成功地map()返回一个可观察对象,因此你不必一次又一次地输入flatMap()这个map()。是作为速记创建的。它正如普通map()一样应用映射函数,但稍后它不会发出返回值,而是“展平”(或合并)它们。

这是一般用例。它在代码库中最有用,它在整个地方使用Rx并且你有很多方法返回observables,你想用其他方法链接observables。

在您的用例中,它恰好也很有用,因为onNext()只能将onNext()中发出的一个值转换为map()中发出的另一个值。但它无法将其转换为多个值,根本没有值或错误。正如akarnokd在他的回答中所写的那样(并且请注意,他比我更聪明,可能一般,但至少在涉及到RxJava时),你不应该从你的flatMap()中抛出异常。因此,您可以使用return Observable.just(value);

return Observable.error(exception);

一切顺利,但

{
  "query": {
    "bool": {
      "should": [
        {
          "query_string": {
            "default_field": "content",
            "query": "Lorem ipsum dolor sit amet"
          }
        },
        {
          "query_string": {
            "default_field": "content",
            "query": "Nunc ac auctor massa"
          }
        }
      ]
    }
  }
}
当事情失败时

有关完整代码段,请参阅他的答案:https://stackoverflow.com/a/30330772/1402641

答案 4 :(得分:16)

以下是一个简单的拇指规则,我帮助我决定何时在Rx的flatMap()中使用map()而不是Observable

一旦你决定采用map转换,你就会编写转换代码来返回一些Object吗?

如果您转换的最终结果是:

  • 一个不可观察的对象,然后您只使用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))

希望澄清。

答案 5 :(得分:16)

问题是你什么时候在RxJava中使用map vs flatMap?。我认为一个简单的演示更具体。

如果要将发出的项目转换为其他类型,在将文件转换为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可以帮助我们序列化请求。

答案 6 :(得分: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);
        }
    }
});

通常,你应该尽可能避免从onXXX方法和回调中抛出(Runtime-)异常,即使我们在RxJava中放置了尽可能多的安全措施。

答案 7 :(得分:6)

在该场景中使用map,您不需要新的Observable。

您应该使用Exceptions.propagate,它是一个包装器,因此您可以将这些已检查的异常发送到rx机制

function Person() {
  this.cars = {};
}
function Car(make, country) {
  this.make = make;
  this.country = country;
}
Car.prototype.instantiate = function(currentPrice, miles) {
  var instance = Object.create(this);
  instance.currentPrice = currentPrice;
  instance.miles = miles;
  return instance;
};
var ferrari = new Car('Ferrari', 'Italy');
var fred = new Person();
fred.cars.ferrari = ferrari.instantiate(1200, 300000);

然后您应该在订阅者

中处理此错误
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
    } 
} 
});

有一篇优秀的帖子:http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/

答案 8 :(得分:0)

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

答案 9 :(得分:0)

平面图将可观察对象映射到可观察对象。 将项目映射到项目。

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

如果您正在执行任何异步操作(包括切换线程),则应使用Flatmap,因为Map不会检查使用者是否被处置(部分重量轻)

答案 10 :(得分:0)

RxJava Map 与 FlatMap

它们都是转换运算符,但 const input = { Product: "abc", Data: "{\"Name\":\"John\",\"Email\":\"john@example.com\"}" }; const variables = Object.entries(input).reduce((output, [key, value]) => { output[key] = { type: typeof value, value }; return output; }, {}); const result = { variables, key: '123' }; console.log(result); 具有 1-1 关系,而 map 具有 1-0 或多个关系。

  • flatMapmap 发出 stream,其中 flatmap 只包含 1 个元素或 map
  • 发出 0/many 元素
  • flatmap 发出单个元素,map 发出元素流

地图运算符

flatmap

FlatMap 运算符

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

[flatMap vs concatMap]