RxJava 操作符flatMap 与 concatMap详解

本文独家发布到公众号:Android技术杂货铺

RxJava 操作符flatMap 与 concatMap详解_第1张图片
封面图-pixabay

近两年来,RxJava可以说是异常的火爆,受到众多开发者的追捧与青睐,虽然后入门的门槛较高,学习成本较大,但是还是掀起一场学习Rxjava的狂潮。为什么呢?因为RxJava的特性:轻松的线程切换、流式的API写法和强大的操作符。这使得我们做异步操作变得很简单,不用像以前一样写各种Handler来回调主线程,只需要一个操作符一行代码就搞定。流式的API使我们的逻辑变得非常清晰,可读性很强。因此,RxJava也是我们项目重构的利器。

说到RxJava强大的操作符,那就不得不提flatMap了,那么篇文章就简单谈谈flatMap的使用场景和它与另一个操作符concatMap的区别。

由于现在RxJava已经发布2.x版本了,因此本文我们使用Rxjava 的 2.x 版本写所有的示例。build.gradle中依赖最新版本:

 compile 'io.reactivex.rxjava2:rxjava:2.1.0'
 compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

一、操作符 flatMap详解

flatMap将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable。


RxJava 操作符flatMap 与 concatMap详解_第2张图片

flatMap使用一个指定的函数对原始Observable发射的每一项数据之行相应的变换操作,这个函数返回一个本身也发射数据的Observable,然后FlatMap合并这些Observables发射的数据,最后将合并后的结果当做它自己的数据序列发射。

这个方法是很有用的,例如,当你有一个这样的Observable:它发射一个数据序列,这些数据本身包含Observable成员或者可以变换为Observable,因此你可以创建一个新的Observable发射这些次级Observable发射的数据的完整集合。

上面是官方关于flatMap的解释,如果只看这一段话,是不是有点难以理解呢?是的,可能是有点蒙.没关系,我们先来看一个它的使用场景,然后我们倒回来看一下解释,可能你就清楚它是干什么的了。

我们假设有这个一场景:每个学校都有成绩统计系统,有这样一个需求,我们要抽取一个班,打印该班的每个同学的每一门课程成绩。

首先根据需求,抽取2个实体:学生实体和课程实体,如下:

/**
 * Created by zhouwei on 17/6/19.
 */

public class Student {
    public String name;//学生名字
    public int id;
    public List mSources;//每个学生的所有课程

    public Student(String name, int id, List sources) {
        this.name = name;
        this.id = id;
        mSources = sources;
    }
}

课程实体:

public class Source {
    public int sourceId;//id
    public String name;//课程名
    public int score;//成绩

    public Source(int sourceId, String name, int score) {
        this.sourceId = sourceId;
        this.name = name;
        this.score = score;
    }
}

按照传统的方式:

  //开启一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 从服务器获取班级所有同学信息
                List students = MockData.getAllStudentInfoById(0);

                for(int i=0;i sources = students.get(i).mSources;

                    for (int index=0;index

以上就是传统的实现方式,可以看到整个代码的结构是非常难看的,如果在加一些其他的过滤条件,要打印指定某一个人的某一门课程的成绩?那么我们又会嵌套if-else判断,这种多层的循环嵌套使得我们的代码可读性变得非常差。怎么解决呢?flatMap操作符就是专门为这个而生的,我们来看一下用flatMap来改造一下:

Flowable.fromIterable(MockData.getAllStudentInfoById(0))
            .flatMap(new Function>() {
                @Override
                public Publisher apply(@NonNull Student student) throws Exception {
                    return Flowable.fromIterable(student.mSources);
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer() {
                @Override
                public void accept(@NonNull Source source) throws Exception {
                    String content = "sourceName:"+source.name +" source score:"+source.score;
                    mTextView.setText(content);
                    Log.i(TAG,content);

                }
            });

这样是不是看起来就舒服多了呢?每一步做了什么我们都看得很清楚,整个流程非常的清晰。

打印的结果是这样如下(3位同学):

RxJava 操作符flatMap 与 concatMap详解_第3张图片

那么我们结合这个示例和上面的图我们在回过头来理解一下官方对flatMap的解释

RxJava 操作符flatMap 与 concatMap详解_第4张图片

解释:上图中的圆表示的就是原始数据,中间是flatMap操作符执行的变换(将圆变换为一个菱形和正方形),函数返回的是一个可以发射数据(菱形和正方形)的Observable,最合后并这些发送数据的Observable。那么映射到上面的这个示例就是:List< Student > 代表圆,然后`Flowable.fromIterable(student.mSources)`就是flatMap执行的变换操作,返回的就是可以发射Source数据的Observable(本文用的是Flowable),最后合并后的结果就是自己的数据序列( 也就是上图中打印的3个同学的各门成绩)。

这样应该是说清楚了flatMap的作用了吧?如果还没理解,多看几遍图,看图百遍,其义自现。

注意:如果任何一个通过这个flatMap操作产生的单独的Observable调用onError异常终止了,这个Observable自身会立即调用onError并终止。

flatMap的特点:其实从上面的图就可以看出,经过flatMap操作变换后,最后输出的序列有可能是交错的,因为flatMap最后合并结果采用的是merge操作符。如果要想经过变换后,最终输出的序列和原序列一致,那就会用到另外一个操作符,concatMap

二、操作符 concatMap介绍以及与 flatMap的区别

RxJava 操作符flatMap 与 concatMap详解_第5张图片

concatMap操作符的功能和flatMap是非常相似的,只是有一点,concatMap 最终输出的数据序列和原数据序列是一致,它是按顺序链接Observables,而不是合并(flatMap用的是合并)。

我们来看一个例子:Observable 发射5个数据(1,2,3,4,5),然后分别用flatMapconcatMap 对它执行一个变换( *10),然后再输出结果序列。

flatMap:

Observable.fromArray(1,2,3,4,5)
                .flatMap(new Function>() {
                    @Override
                    public ObservableSource apply(@NonNull Integer integer) throws Exception {

                        int delay = 0;
                        if(integer == 3){
                            delay = 500;//延迟500ms发射
                        }
                        return Observable.just(integer *10).delay(delay, TimeUnit.MILLISECONDS);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                Log.e("zhouwei","accept:"+integer);
            }
        });

输出结果序列如下图:



为了更真实的模拟,我们将第三个数据延迟500ms发射,我们看到,最终的结果出现了交错。(如果与原序列一致的话应该是:10,20,30,40,50)。

那么我们用同样的代码,将flatMap换成concatMap 看一下:

 Observable.fromArray(1,2,3,4,5)
                .concatMap(new Function>() {
                    @Override
                    public ObservableSource apply(@NonNull Integer integer) throws Exception {

                        int delay = 0;
                        if(integer == 3){
                            delay = 500;//延迟500ms发射
                        }
                        return Observable.just(integer *10).delay(delay, TimeUnit.MILLISECONDS);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                Log.e("zhouwei","accept:"+integer);
            }
        });

看一下concatMap的结果序列:

RxJava 操作符flatMap 与 concatMap详解_第6张图片

可以看到经过 concatMap变换后的数据序列 与 原数据序列的顺序是保持一致的。

小结: concatMapflatMap的功能是一样的, 将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据放进一个单独的Observable。只不过最后合并ObservablesflatMap采用的merge,而concatMap采用的是连接(concat)。总之一句一话,他们的区别在于:concatMap是有序的,flatMap是无序的,concatMap最终输出的顺序与原序列保持一致,而flatMap则不一定,有可能出现交错。

三、总结

flatMap是Rxjava中一个强大的操作符,在实际项目中,应用的场景很多,比如开始列举的化解循环嵌套,还有一种场景在我们实际项目中是非常多的,那就是连续请求两个接口,第一个接口的返回值是第二个接口的请求参数,在这种情况下,以前我们会在一个请求完成后,在onResponse中获取结果再请求另一个接口。这种接口嵌套,代码看起来是非常丑陋的,运用flatMap就能很好的解决这个问题。代码看起来非常优雅而且逻辑清晰。 如果需要保证顺序的话,请使用concatMap

以上就是concatMapflatMap 使用场景介绍及区别与联系,如有什么问题,欢迎指正。

参考
ReactiveX

你可能感兴趣的:(RxJava 操作符flatMap 与 concatMap详解)