你什么时候在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的错误处理(不是详细程度)。我还没有看到任何关于这种情况的最佳实践,我很好奇这是如何在实践中使用的。
答案 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。
要点:
这里可以看到一个明显的例子: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对象。此方法的返回类型可以是String
或Observable<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 或多个关系。
flatMap
和 map
发出 stream,其中 flatmap
只包含 1 个元素或 map
flatmap
发出单个元素,map
发出元素流地图运算符
flatmap
FlatMap 运算符
map(new Function<A, B>() {
@Override
public User apply(A a) throws Exception {
B b = new B(a);
return b;
}
})