RxJava 操作符flatMap 与 concatMap详解

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

封面图-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。



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<Source> mSources;//每个学生的所有课程

    public Student(String name, int id, List<Source> 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<Student> students = MockData.getAllStudentInfoById(0);

                for(int i=0;i<students.size();i++){
                    List<Source> sources = students.get(i).mSources;

                    for (int index=0;index<sources.size();index++){
                        final Source source = sources.get(index);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                //主线程更改UI
                                String content = "sourceName:"+source.name +" source score:"+source.score;
                                mTextView.setText(content);
                                Log.i(TAG,content);  
                            }
                        });
                       
                    }
                }
            }
        }).start();

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

Flowable.fromIterable(MockData.getAllStudentInfoById(0))
            .flatMap(new Function<Student, Publisher<Source>>() {
                @Override
                public Publisher<Source> apply(@NonNull Student student) throws Exception {
                    return Flowable.fromIterable(student.mSources);
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Consumer<Source>() {
                @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位同学):

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

解释:上图中的圆表示的就是原始数据,中间是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的区别

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<Integer, ObservableSource<Integer>>() {
                    @Override
                    public ObservableSource<Integer> 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<Integer>() {
            @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<Integer, ObservableSource<Integer>>() {
                    @Override
                    public ObservableSource<Integer> 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<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                Log.e("zhouwei","accept:"+integer);
            }
        });

看一下concatMap的结果序列:


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

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

三、总结

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

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

参考
ReactiveX

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容