RxJava操作符之转换操作符(四)

前言

上一篇文章我们学习了创建类操作符,本篇我们将一起来学习RxJava转换类操作符。所谓转换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。下面来看下转换类操作符都有哪些及其使用场景。

初始化数据

还是使用系列第一篇的小区与房源的例子。先初始化假数据以便实践操作符时使用。

//小区实体
public class Community {
    private String communityName; //小区名称
    private List<House> houses; //房源集合
}
//房源实体
public class House {
    private float size; //大小
    private int floor; //楼层
    private int price; //总价
    private String decoration; //装修程度
    private String communityName; //小区名称
}
private List<Community> communities;

private void initData() {
    communities = new ArrayList<>();
    List<House> houses1 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses1.add(new House(105.6f, i, 200, "简单装修", "东方花园"));
        } else {
            houses1.add(new House(144.8f, i, 520, "豪华装修", "东方花园"));
        }
    }
    communities.add(new Community("东方花园", houses1));
    
    List<House> houses2 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses2.add(new House(88.6f, i, 166, "中等装修", "马德里春天"));
        } else {
            houses2.add(new House(123.4f, i, 321, "精致装修", "马德里春天"));
        }
    }
    communities.add(new Community("马德里春天", houses2));
    
    List<House> houses3 = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        if (i % 2 == 0) {
            houses3.add(new House(188.7f, i, 724, "豪华装修", "帝豪家园"));
        } else {
            houses3.add(new House(56.4f, i, 101, "普通装修", "帝豪家园"));
        }
    }
    communities.add(new Community("帝豪家园", houses3));
}

转换操作符

Map

map操作符,接收一个指定的Func1类型对象,然后将其应用到每一个由Observable发射的值上,进而将发射的值转换为我们期望的值。来看一下原理图与实例:

//将一组Integer转换成String
Observable.just(1, 2, 3, 4, 5)
        .map(new Func1<Integer, String>() {
            @Override
            public String call(Integer integer) {
                return "This is " + integer;
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                Log.e("rx_test", s);
            }
        });
        
//将Community集合转换为每一个Community并获取其name
Observable.from(communities)
        .map(new Func1<Community, String>() {
            @Override
            public String call(Community community) {
                return community.getCommunityName();
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String communityName) {
                Log.e("rx_test", "小区名称为:" + communityName);
            }
        });

输出结果:

This is 1
This is 2
This is 3
This is 4
This is 5
小区名称为:东方花园
小区名称为:马德里春天
小区名称为:帝豪家园

由输出结果可看出,map操作符可用来进行数据的类型转换,拼接或者对集合进行遍历等1对1的转换。第一个例子中,Func1<Integer, String>()第一个参数是发射数据当前的类型,第二个参数是转换之后的数据类型。Action1<String>中参数也为发射数据转换之后的数据类型。

注意数据类型需对应准确,不要弄错了。

FlatMap

flatMap操作符,也是用来转换的,但与map操作符不同之处是,flatMap()返回的是Observable对象,且这个Observable对象并不是被直接发送到了 Subscriber的回调方法中。

这么说可能不易理解,我们来看小区与房的例子,现在有3个小区,如果我们想打印出这3个小区中所有房源的信息,通过RxJava要如何做到?按照之前学习的我们或许会这么实现:

Observable.from(communities)
        .subscribe(new Action1<Community>() {
            @Override
            public void call(Community community) {
                for (House house : community.getHouses()) {
                    Log.e("rx_test", "flatMap:小区名称:" + house.getCommunityName() 
                        + ",价格:" + house.getPrice() + ",楼层:" + house.getFloor());
                }
            }
        });

按照这种实现方法我们只可获取到每个小区这一层,想要获取小区中的房源还需进行一层for循环遍历,这就违背了RxJava的原则了。那么来看下flatMap()如何实现:

Observable.from(communities)
        .flatMap(new Func1<Community, Observable<House>>() {
            @Override
            public Observable<House> call(Community community) {
                return Observable.from(community.getHouses());
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "flatMap:小区名称:" + house.getCommunityName()
                    + ",价格:" + house.getPrice() + ",楼层:" + house.getFloor());
            }
        });

这样的代码是不是看起来舒心多了,再来看下flatMap()是如何实现的。

首先from()接收到小区集合communities后为其创建了一个Observable,依次将每个小区传递给flatMap(),flatMap()在每次接收到小区后会将其中包含的房源集合拿出来又创建了一个房源Observable,并激活这个房源Observable让其开始发射事件,之后返回给小区集合的Observable,最后小区集合的Observable再将这些事件统一交给Subscriber的回调方法去处理。

整个过程有两级Observable在运作,相当于将小区集合Observable这个初始对象铺平之后再通过统一路径分发下去,铺平这个工作就是flatMap所做的。

输出结果:

flatMap:小区名称:东方花园,价格:200,楼层:0
flatMap:小区名称:东方花园,价格:520,楼层:1
flatMap:小区名称:东方花园,价格:200,楼层:2
flatMap:小区名称:东方花园,价格:520,楼层:3
flatMap:小区名称:东方花园,价格:200,楼层:4
flatMap:小区名称:马德里春天,价格:166,楼层:0
flatMap:小区名称:马德里春天,价格:321,楼层:1
flatMap:小区名称:马德里春天,价格:166,楼层:2
flatMap:小区名称:马德里春天,价格:321,楼层:3
flatMap:小区名称:马德里春天,价格:166,楼层:4
flatMap:小区名称:帝豪家园,价格:724,楼层:0
flatMap:小区名称:帝豪家园,价格:101,楼层:1
flatMap:小区名称:帝豪家园,价格:724,楼层:2
flatMap:小区名称:帝豪家园,价格:101,楼层:3
flatMap:小区名称:帝豪家园,价格:724,楼层:4

由输出结果可看出这3个小区的所有房源信息都被依次打印了出来,但flatMap()有一个问题就是当数据量过大时可能会出现输出数据顺序交错的问题。

官方原理图:

ConcatMap

concatMap操作符,与flatMap()功能类似。不同之处是concatMap()采用连接方式而不是合并方式,所以其发射的数据是严格按照顺序的,这就解决了flatMap()有可能发生数据交错的问题。

原理图:

FlatMapIterable

flatMapIterable操作符,也与flatMap()相似,不同之处在于flatMapIterable转化多个Observable是使用Iterable作为源数据的。

Observable.from(communities)
        .flatMapIterable(new Func1<Community, Iterable<House>>() {
            @Override
            public Iterable<House> call(Community community) {
                return community.getHouses();
            }
        }).subscribe(new Action1<House>() {
    @Override
    public void call(House house) {
        Log.e("rx_test", "flatMap:小区名称:" + house.getCommunityName()
                    + ",价格:" + house.getPrice() + ",楼层:" + house.getFloor());
    }
});

SwitchMap

switchMap转换操作符,也与flatMap()相似,每当源Observable发射新数据项(Observable)时,它将取消订阅并停止监视之前那个数据项产生Observable,并开始监视当前发射的这一个。

Observable.from(communities)
        .switchMap(new Func1<Community, Observable<House>>() {
            @Override
            public Observable<House> call(Community community) {
                return Observable.from(community.getHouses());
            }
        })
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "flatMap:小区名称:" + house.getCommunityName()
                    + ",价格:" + house.getPrice() + ",楼层:" + house.getFloor());
            }
        });

如之前的例子,当数据量很大时,某一时刻,第一个小区所生成的小房源Observable正在发射数据,这时第二个小区所生成的小房源Observable被激活,则第一个小区的小Observable就会被取消订阅,其还未发射的数据也不在发射了。第二个小区小Observable开始发射数据,之后都同理。

原理图:


Scan

scan操作符,对一个序列的数据应用一个函数,并将这个函数的结果发射出去作为下个数据应用函数时的第一个参数使用。

//例如:先输出1,再将1+2=3作为下个数据发出,3+3=6再作为下个数据发出,以此类推。
Observable.just(1, 2, 3, 4, 5)
        .scan(new Func2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        })
        .subscribe(new Action1<Integer>() {
            @Override
            public void call(Integer integer) {
                Log.e("rx_test", "scan:" + integer);
            }
        });

输出结果:

scan:1
scan:3
scan:6
scan:10
scan:15

原理图:

GroupBy

groupBy操作符,将原始Observable发射的数据按照key来拆分成一些小的Observable,然后这些小的Observable分别发射其所包含的的数据。通俗的说就是按照某个字段将数据进行分类再发射。

来看一个例子:有几个小区的多套房源数据,现在需要将其按照小区名称进行分类并输出。

List<House> houseList = new ArrayList<>();
houseList.add(new House(105.6f, 1, 200, "简单装修", "东方花园"));
houseList.add(new House(144.8f, 3, 300, "豪华装修", "马德里春天"));
houseList.add(new House(88.6f, 2, 170, "简单装修", "东方花园"));
houseList.add(new House(123.4f, 1, 250, "简单装修", "帝豪家园"));
houseList.add(new House(144.8f, 6, 350, "豪华装修", "马德里春天"));
houseList.add(new House(105.6f, 4, 210, "普通装修", "东方花园"));
houseList.add(new House(188.7f, 3, 400, "精致装修", "帝豪家园"));
houseList.add(new House(88.6f, 2, 180, "普通装修", "东方花园"));
//根据小区名称进行分类
Observable<GroupedObservable<String, House>> groupByCommunityNameObservable = Observable
        .from(houseList)
        .groupBy(new Func1<House, String>() {
            @Override
            public String call(House house) {
                //提供分类规则的key
                return house.getCommunityName();
            }
        });
Observable.concat(groupByCommunityNameObservable) //concat组合操作符,将多个Observable有序组合并发送,后期会详细讲解
        .subscribe(new Action1<House>() {
            @Override
            public void call(House house) {
                Log.e("rx_test", "groupBy:" + "小区:" + house.getCommunityName() + ",价格:" + house.getPrice());
            }
        });

创建一个新的Observable:groupByCommunityNameObservable,它将会发送一个带有GroupedObservable的序列(也就是指发送的数据项的类型为GroupedObservable)。GroupedObservable是一个特殊的Observable,它基于一个分组的key,在这个例子中的key就是小区名。

输出结果:

groupBy:小区:东方花园,价格:200
groupBy:小区:东方花园,价格:170
groupBy:小区:东方花园,价格:210
groupBy:小区:东方花园,价格:180
groupBy:小区:马德里春天,价格:300
groupBy:小区:马德里春天,价格:350
groupBy:小区:帝豪家园,价格:250
groupBy:小区:帝豪家园,价格:400

原理图:

总结

到此,本篇关于RxJava的常用转换类操作符就讲解完毕了,下一篇我们将一起研究RxJava的四类操作符中的过滤操作符都有哪些以及如何使用。

技术渣一枚,有写的不对的地方欢迎大神们留言指正,有什么疑惑或者建议也可以在我Github上RxJavaDemo项目Issues中提出,我会及时回复。

附上RxJavaDemo的地址:
RxJavaDemo

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

推荐阅读更多精彩内容