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


180

什么时候在RxJava中使用mapvs ?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(不是冗长)。我还没有看到关于此的最佳实践,我很好奇在实践中如何使用它。

Answers:


121

map将一个事件转换为另一个事件。 flatMap将一个事件转换为零个或多个事件。(摘自IntroToRx

当您想将json转换为对象时,使用map应该就足够了。

处理FileNotFoundException是另一个问题(使用地图或平面地图无法解决此问题)。

要解决您的Exception问题,只需将其与Non Checked异常一起抛出: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));
        }
    }
});

2
这不会调用subscriber.onError()等。我所看到的所有示例都以这种方式路由了错误。没关系吗?
克里斯托弗·佩里

7
请注意,OnErrorThrowableare 的构造函数,private您需要使用OnErrorThrowable.from(e)
david.mihola 2015年

我刚刚更新。OnErrorThrowable.from(e)不保留该值,因此我改用OnErrorThrowable.addValueAsLastCause(e,file),它应保留该值。
dwursteisen 2015年

1
我喜欢这些代码示例,但是如果您更新flatMap调用的签名以返回Observable <String>而不是String,则将有所帮助……因为从技术上讲两者之间的区别不是吗?
Rich Ehmer

78

FlatMap的行为非常类似于map,不同之处在于它所应用的函数本身返回一个可观察的对象,因此它非常适合于在异步操作上进行映射。

从实际意义上讲,函数Map只是对链接的响应进行了转换(不返回Observable)。FlatMap Applying函数返回一个Observable<T>,这就是为什么如果您打算在方法内部进行异步调用的话,建议使用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返回相同的类型,但是它们应用的函数返回不同的类型。
CoXier

61

在您的情况下,您需要地图,因为只有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将成为异步调用。然后,您需要将地图更改为flatMap。将其保留为地图将意味着您不会像预期那样触发事件,并且会引起混乱。我一直在学习RX Java,因此被这件事困扰了几次。我发现flatMap是确保事情按您期望的方式处理的可靠方式。
user924272

24

我考虑的方式是,flatMap当要放入其中的函数map()返回时使用Observable。在这种情况下,您仍然可以尝试使用,map()但这是不切实际的。让我尝试解释原因。

如果在这种情况下你决定坚持下去map,你会得到一个回报Observable<Observable<Something>>。例如,在您的情况下,如果我们使用一个假想的RxGson库,该库Observable<String>从其toJson()方法中返回了a(而不是简单地返回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提供了一些扁平化可观察对象的方法,为简单起见,让我们假设合并是我们想要的。合并基本上需要一堆可观察对象,并且只要它们中的任何一个发出就发出。(很多人认为切换将是更好的默认值。但是,如果您仅发出一个值,则没关系。)

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

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

在您的用例中,它恰好也很有用,因为map()只能将发出的一个值转换onNext()成发出的另一个值onNext()。但是它不能将其转换为多个值,完全没有值或错误。正如akarnokd在他的回答中所写(请记住,他通常比我聪明,但总的来说,但至少在RxJava方面),您不应从抛出异常map()。因此,您可以使用flatMap()

return Observable.just(value);

当一切顺利的时候,但是

return Observable.error(exception);

当某件事失败时。
请参阅他的答案以获取完整的摘录:https : //stackoverflow.com/a/30330772/1402641


1
这是我最喜欢的答案。您基本上最终将可观察的嵌套在可观察的IF中,这是您的方法返回的结果。
filthy_wizard

21

问题是您何时在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应用函数中,首先我从中获取用户ID,Response然后获取用户信息。完成两个请求后,我们可以完成更新UI或将数据保存到数据库之类的工作。

但是,如果您使用map它,就无法编写如此出色的代码。总之,flatMap可以帮助我们序列化请求。


18

这是一个简单的经验法则,可以帮助我决定何时在Rx的中使用flatMap()over 。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))

希望能澄清。


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

通常,即使我们在RxJava中放置了尽可能多的防护措施,也应尽可能避免从onXXX方法和回调中引发(Runtime-)异常。


但是我认为地图就足够了。那么flatMap和map是一个习惯吧?
CoXier

6

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


0

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


0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.