Android :RxJava学习笔记之转换操作符

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android :RxJava学习笔记之转换操作符相关的知识,希望对你有一定的参考价值。

转换操作符

转换类操作符顾名思义,就是将上游(被观察者)发送的数据流进行转换成另外一种形式(观察者希望接收到的形式),从而使下游(观察者)能够正确的接受并作出响应。

这里介绍map,cast,flatMap,concatMap,flatMapIterable,switchMap,scan,buffer,window,groupBy进行讲解

Student类

public class Student {
  private String name;
  private int score;

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

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getScore() {
    return score;
  }

  public void setScore(int score) {
    this.score = score;
  }

}

1、map

map的方法声明为:Observable<R> map(Func1<? super T, ? extends R> func),其中Func1<T, R>接口里面只包含一个R call(T t)方法,从参数和返回值就可以看出来这是一个将T类型转换成一个R类型的方法。FuncX类的接口表示有X+1个泛型,前X个是call方法的参数,最后一个是call方法的返回值。

所以map的作用就是将接收类型为TObservable转换为接收类型为RObservable

Map的使用也很简单,比如说现在有一个Student列表,上游发出的数据流是Student格式,但是下游希望接收到的格式是Student对象的成绩(Integer格式),代码如下:

List<Student> studentList = new ArrayList<>();
    studentList.add(new Student("U1111", 59));
    studentList.add(new Student("U1112", 69));
    studentList.add(new Student("U1113", 37));
    studentList.add(new Student("U1114", 72));
    studentList.add(new Student("U1115", 26));
    studentList.add(new Student("U1116", 98));
    studentList.add(new Student("U1117", 53));
    studentList.add(new Student("U1118", 60));
    studentList.add(new Student("U1119", 100));

    Observable
        .fromIterable(studentList)
        .map(new Function<Student, Integer>() {
          @Override
          public Integer apply(Student student) throws Exception {
            return student.getScore();
          }
        })
        .subscribe(new Consumer<Integer>() {
          @Override
          public void accept(Integer integer) throws Exception {
            System.out.println("成绩为:" + integer);
          }
        });

输出

成绩为:59
成绩为:69
成绩为:37
成绩为:72
成绩为:26
成绩为:98
成绩为:53
成绩为:60
成绩为:100

上述代码可以理解为from传入数据流后,经过map函数,转换成Integer,再到达subscriber,调用事件回调函数。

2、cast

cast运算符作用和map类似,但是它只能将父类对象转换为子类对象,是map的特殊版本。
方法声明:Observable<R> cast(final Class<R> klass),将源事件类型转换成klass的数据类型。

  • 注意只能将父类对象转为子类对象
  • cast执行转化必须是可以转的,如果该父类对象无法转为子类对象,会抛出ClassCastException异常

Person是Student的父类:

List<Person> personList = new ArrayList<>(studentList);
Observable.from(personList)
    .cast(Student.class)
    .subscribe(student -> System.out.println(student.getId()));

3、flatMap

现在比如给出一个年级的所有班级的列表ArrayList,每个IClass对象都包含内部学生的列表ArrayList,我要输出每个班人的成绩。我当然可以这样做:

Observable.from(iClassList)
    .subscribe(new Action1<IClass>() {
      @Override
      public void call(IClass iClass) {
        for (Student student : iClass.getStudentList()) {
          System.out.println(student.getGrade());
        }
      }
    });

那么如果我不想在事件回调的时候去遍历,我就想让事件回调时接受到的就是每个Student,而不是一个班级IClass,该如何做呢?

这个时候就需要用到flatMap这个操作符了,先看看具体的实现:

Observable.from(iClassList)
    .flatMap(new Func1<IClass, Observable<Student>>() {
      @Override
      public Observable<Student> call(IClass iClass) {
        return Observable.from(iClass.getStudentList());
      }
    })
    .subscribe(new Action1<Student>() {
      @Override
      public void call(Student student) {
        System.out.println(student.getGrade());
      }
    });

flatMap的参数形式为Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func),传入一个T类型,返回一个接收事件为R类型的Observable,在结合上面的例子,就很好理解flatMap的作用了。

上述代码调用流程:

  • flatMap依次将传入的每一个iClass对象转换为一个Observable<Student>对象,在这个Observable中传入iClass.getStudentList()事件列表
  • 最后将所有Observable<Student>对象中的事件都汇集到一个Observable之中,这个Obsevable就是flatMap的返回值,由这个Observable将所有的事件发给Subscriber,Subscriber再对每个事件依次调用事件回到函数。

flatMap的缺点在于它并不一定会按照原始Observable的顺序发送事件,可能会有事件的交叉发送的情况。

原理图如下:

4、concatMap

上面也讲到了flatMap的缺点,那么如何解决交叉的问题呢?RxJava给我们提供了concatMap这个方法,它用来将最初发送的事件按照原来的顺序连续发送出去。使用方法跟flatMap一样。

Observable<R> concatMap(Func1<? super T, ? extends Observable<? extends R>> func)

4.1、concatMap和flatmap对比

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

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

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(Schedulers.newThread())
        .subscribe(new Consumer<Integer>() {
          @Override
          public void accept(@NonNull Integer integer) throws Exception {
            System.out.println("accept:"+integer);
          }
        });
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

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

输出

accept:10
accept:20
accept:40
accept:50
accept:30

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

 Observable.fromArray(1,2,3,4,5)
        .concatMap((Function<Integer, ObservableSource<Integer>>) integer -> {

          int delay = 0;
          if(integer == 3){
            delay = 500;//延迟500ms发射
          }
          return Observable.just(integer *10).delay(delay, TimeUnit.MILLISECONDS);
        })
        .subscribeOn(Schedulers.io())
        .observeOn(Schedulers.newThread())
        .subscribe(integer -> System.out.println("accept:"+integer));
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

输出

accept:10
accept:20
accept:30
accept:40
accept:50

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

小结:

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

5、flatMapIterable

flatMapIterable跟flatMap相差无几,区别在于flatMapIterable转换后的是一个Iterable对象,最后将所有的Iterable都传入一个Observable中,由这个Observable来将事件发送出去。

原理图如下:

Observable.from(iClassList)
    .flatMapIterable(new Func1<IClass, Iterable<Student>>() {
      @Override
      public Iterable<Student> call(IClass iClass) {
        return iClass.getStudentList();
      }
    })
    .subscribe(new Action1<Student>() {
      @Override
      public void call(Student student) {
        System.out.println(student.getGrade());
      }
    });

6、scan

scan就是将前一个事件产生的结果发送到下一个事件转换时的第一个参数使用。

Observable<T> scan(Func2<T, T, T> accumulator) 
Observable<R> scan(R initialValue, Func2<R, ? super T, R> accumulator) 

Func2接口的三个泛型,前两个是内部call方法的调用参数,第三个是call的返回值

先说第一个重载,第一个T是前一个事件经过Func2.call方法后的返回值,第二个T是当前事件,第三个T是调用call方法后的返回值,作为下一个事件的Func2的第一个参数,至于第一个事件就是默认无初始计算结果了。第二个重载就是给第一个事件设置一个初始的计算结果。

代码如下:

 Observable
          .just(1, 2, 3, 4, 5)
          .scan(new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) throws Exception {
              return integer + integer2;
            }
          })
          .doOnComplete(new Action() {
            @Override
            public void run() throws Exception {
              System.out.println("Sequence complete");
            }
          })
          .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) throws Exception {
              System.out.println("Success: " + integer);
            }
          }
          );
  }
简化
Observable
          .just(1, 2, 3, 4, 5)
          .scan((integer, integer2) -> integer + integer2)
          .doOnComplete(() -> System.out.println("Sequence complete"))
          .subscribe(integer -> System.out.println("Success: " + integer)
          );

输出

Success: 1
Success: 3
Success: 6
Success: 10
Success: 15
Sequence complete

7、buffer

buffer就是将原有的事件转换成一个List列表,由一个新的Observable一次性发送出去,方法声明如下:

Observable<List<T>> buffer(int count)
Observable<List<T>> buffer(int count, int skip)

第一种重载内部就是调用了buffer(count, count),所以就直接分析第二个重载。
countskip的含义为:

  • 把每skip个事件中的前count个事件转换成一个List列表,剩余的丢弃,再由新的Observable将这个列表发送出去。
  • 比如现在有一个StudentList,id分别是从U1001到U1008,总共八个数据,如果调用如下代码:
List<Student> studentList = new ArrayList<>();
    studentList.add(new Student("U1111", 59));
    studentList.add(new Student("U1112", 69));
    studentList.add(new Student("U1113", 37));
    studentList.add(new Student("U1114", 72));
    studentList.add(new Student("U1115", 26));
    studentList.add(new Student("U1116", 98));
    studentList.add(new Student("U1117", 53));
    studentList.add(new Student("U1118", 60));
    studentList.add(new Student("U1119", 100));
    Observable
        .fromIterable(studentList)
        .buffer(4, 5)
        .subscribe(new Consumer<List<Student>>() {
          @Override
          public void accept(List<Student> students) throws Exception {
            for(Student student : students){
              System.out.println(student.getName());
            }
          }
        });
  }

简化

Observable
        .fromIterable(studentList)
        .buffer(4, 5)
        .subscribe(students -> {
          for(Student student : students){
            System.out.println(student.getName());
          }
        });

输出

U1111
U1112
U1113
U1114
U1116
U1117
U1118
U1119

U1005是第一组5个中的第5个被丢弃了,U1006-U1008是第二组5个中的前三个,由于第4个不存在,所以发送第三个后就结束发送了。

8、window

window与buffer的作用非常相似,区别在于buffer发送出去的是一个列表List,而window发送出去的事件是一个Observable对象,方法声明如下:

Observable<Observable<T>> window(int count)
Observable<Observable<T>> window(int count, int skip)

所以在window的subscribe中得到的是一个Observable对象,需要再次调用subscribe方法,去获取其中的数据,代码如下:

List<Student> studentList = new ArrayList<>();
    studentList.add(new Student("U1111", 59));
    studentList.add(new Student("U1112", 69));
    studentList.add(new Student("U1113", 37));
    studentList.add(new Student("U1114", 72));
    studentList.add(new Student("U1115", 26));
    studentList.add(new Student("U1116", 98));
    studentList.add(new Student("U1117", 53));
    studentList.add(new Student("U1118", 60));
    studentList.add(new Student("U1119", 100));
    Observable
        .fromIterable(studentList)
        .window(3, 5)
        .subscribe(
            studentObservable -> studentObservable.subscribe(
                student -> System.out.println(student.getName())));

输出

U1111
U1112
U1113
U1116
U1117
U1118

9、groupBy

groupBy的常用调用方式如下:

Observable<GroupedObservable<K, T>> groupBy(final Func1<? super T, ? extends K> keySelector)

groupBy通过keySelector将发送的事件分为不同的组,并将每一组以Observable<GroupObserverable>的形式发送出去。

keySelector第一个参数为call方法传入的事件类型,第二个参数是返回的key

比如发出的事件源为学生列表,但是我想将其分为及格的和不及格的分别处理,代码如下

List<Student> studentList = new ArrayList<>();
    studentList.add(new Student("U1111", 59));
    studentList.add(new Student("U1112", 69));
    studentList.add(new Student("U1113", 37));
    studentList.add(new Student("U1114", 72));
    studentList.add(new Student("U1115", 26));
    studentList.add(new Student("U1116", 98));
    studentList.add(new Student("U1117", 53));
    studentList.add(new Student("U1118", 60));
    studentList.add(new Student("U1119", 100));
    Observable
        .fromIterable(studentList)
        .groupBy(new Function<Student, Integer>() {
          @Override
          public Integer apply(Student student) throws Exception {
            return student.getScore() >= 60 ? 1 : 0;
          }
        })
        .subscribe(new Consumer<GroupedObservable<Integer, Student>>() {
          @Override
          public void accept(GroupedObservable<Integer, Student> integerStudentGroupedObservable)
              throws Exception {
            integerStudentGroupedObservable.subscribe(new Consumer<Student>() {
              @Override
              public void accept(Student student) throws Exception {
                if(integerStudentGroupedObservable.getKey() == 0){
                  System.out.println("不及格ID: " + student.getName());
                } else {
                  System.out.println("及格ID: " + student.getName());
                }
              }
            });
          }
        });

简化:
Observable
        .fromIterable(studentList)
        .groupBy(student -> student.getScore() >= 60 ? 1 : 0)
        .subscribe(integerStudentGroupedObservable -> integerStudentGroupedObservable.subscribe(
            student -> {
              if(integerStudentGroupedObservable.getKey() == 0){
                System.out.println("不及格ID: " + student.getName());
              } else {
                System.out.println("及格ID: " + student.getName());
              }
            }));

输出

不及格ID: U1111
及格ID: U1112
不及格ID: U1113
及格ID: U1114
不及格ID: U1115
及格ID: U1116
不及格ID: U1117
及格ID: U1118
及格ID: U1119

参考

1、https://blog.csdn.net/boyeleven/article/details/81867256

以上是关于Android :RxJava学习笔记之转换操作符的主要内容,如果未能解决你的问题,请参考以下文章

Android :RxJava学习笔记之过滤操作符

Android :RxJava学习笔记之条件/布尔操作符

Android :RxJava学习笔记之创建操作符

Android :RxJava学习笔记之Single

Android :RxJava学习笔记之 错误处理

Android :RxJava学习笔记之Subject