193

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

4

11 回答 11

127

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));
        }
    }
});
于 2014-04-08T15:45:14.813 回答
82

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

于 2014-12-09T06:06:55.170 回答
68

在您的情况下,您需要地图,因为只有 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+++
于 2015-05-18T21:59:56.070 回答
39

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

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

于 2018-03-09T03:03:18.803 回答
29

我的想法是,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 将是相当棘手的。在它里面你会得到一个Observable<String>你需要再次subscribe()获得价值的东西。这既不实用也不好看。

因此,为了使它有用,一个想法是“扁平化”这个 observable 的 observable(你可能会开始看到名称 _flat_Map 的来源)。RxJava 提供了几种扁平化 observables 的方法,为了简单起见,我们假设merge是我们想要的。Merge 基本上需要一堆 observables 并在它们中的任何一个发出时发出。(很多人会争辩说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价值。(另外,请注意,merge()RxJava 中不存在这样的变体,但如果你理解合并的想法,那么我希望你也理解它是如何工作的。)

所以基本上因为这样merge()可能只在它成功map()返回一个可观察对象时才有用,所以你不必一遍又一遍地输入它,flatMap()它被创建为速记。它像正常一样应用映射函数map(),但后来它不是发出返回值,而是“扁平化”(或合并)它们。

这是一般用例。它在到处使用 Rx 的代码库中最有用,并且您有许多返回 observables 的方法,您希望将它们与返回 observables 的其他方法链接起来。

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

return Observable.just(value);

当一切顺利,但是

return Observable.error(exception);

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

于 2016-05-20T19:34:09.280 回答
24

这是一个简单的经验法则,我使用它来帮助我决定何时在 Rx中使用flatMap()over 。map()Observable

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

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

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

  • 一个Observable对象,然后你会使用flatMap(). 并flatMap()解开 Observable,挑选返回的对象,用它自己的 Observable 包装它并发出它。

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

  • 如果返回类型titleCase(..)是 mere String,那么你会使用map(s -> titleCase(s))

  • 如果返回类型titleCase(..)Observable<String>,那么你会使用flatMap(s -> titleCase(s))

希望澄清。

于 2017-07-06T17:29:07.127 回答
13

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

于 2015-05-19T16:08:35.013 回答
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/

于 2016-04-17T13:19:42.577 回答
4

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 与 concatMap]

[Swift 地图 vs flatMap]

于 2021-05-18T16:40:43.537 回答
1

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

于 2017-08-19T16:31:24.697 回答
1

Flatmap 将 observables 映射到 observables。Map 将项目映射到项目。

Flatmap 更灵活,但 Map 更轻量级和直接,所以它取决于你的用例。

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

于 2019-09-17T00:43:53.443 回答