什么时候在RxJava中使用Map vs FlatMap?

RxJava中,何时使用map,何时使用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,它要详细得多,但我们可以将问题沿着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的错误处理(不是详细程度)。我还没有看到任何关于这方面的最佳实践,我很好奇这是如何在实践中使用的。

130030 次浏览

map将一个事件转换为另一个事件。 flatMap将一个事件转换为零个或多个事件。(摘自内托克斯

如果您想将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本身,因此它非常适合异步操作上的映射。

在实际意义上,应用的函数映射只是对链接的响应进行转换(不返回可观察对象)。尽管函数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,其文档中的解释可能有助于理解一般概念。

要处理错误,请重写用户的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个输出。

映射提供的函数简单地接受一个项目并返回一个项目,该项目将进一步向下发出(仅一次)。

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+++

我只是想补充一下,对于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中放置了尽可能多的保护措施。

在使用Map场景中,您不需要为它创建新的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/

我的想法是,当你想放在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

在这一点上,将ABC_0_到这样一个可观察对象是相当棘手的。在它里面,你会得到一个Observable<String>,你需要再次subscribe()来得到这个值。既不实用也不好看。

因此,为了使其有用,一个想法是“展平”这个可观察对象的可观察对象(您可能会开始看到_平面_映射这个名称的来源)。RxJava提供了几种展平可观测量的方法,为了简单起见,让我们假设合并是我们想要的。“合并”基本上是获取一组可观察对象,并在它们中的任何一个发出时发出。(很多人会说,切换将是一个更好的默认选择。但如果您只发出一个值,那就无所谓了。)

因此,修改我们之前的代码片段,我们将得到:

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

在您的用例中,它恰好也很有用,因为map()只能将onNext()中发出的一个值转换为onNext()中发出的另一个值。但它不能将其转换为多个值、根本没有值或错误。正如阿卡诺克在他的回答中所写的那样(请注意,他可能比我聪明得多,但至少在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),它返回输入参数的带标题的字符串对象。此方法的返回类型可以是StringObservable<String>

  • 如果titleCase(..)的返回类型仅仅是String,那么您应该使用map(s -> titleCase(s))

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

希望这能澄清。

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

问题什么时候在RxJava中使用Map vs 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可以帮助我们序列化请求。

FlatMap将可观测量映射到可观测量。 映射将项目映射到项目。

FlatMap更加灵活,但Map更加轻量级和直接,因此它在某种程度上取决于您的用例。

如果您正在执行任何异步操作(包括切换线程),则应该使用FlatMap,因为Map不会检查使用者是否已释放(轻量级的一部分)。

RxJava Map与FlatMap

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

  • mapflatmap发出小溪
    • 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]