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

763 阅读9分钟

前言

  上一篇文章我们学习了创建类操作符,本篇我们将一起来学习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