简介
记录完了lift,再来看看另外两个经常被混淆的概念——map与flatmap。map的定义很容易理解,可以将它就是lift的“人性化”的延伸,但是flatmap就比较难以理解了,“扁平化map“是个啥?
map介绍
在说map之前,需要提到的是lift实现有个很容易让初学者出现错觉的地方。那就是新数据类型对老数据类型的转换(R->T),需要把新数据关系映射到老数据上去,这个是比较反人类的。而使用lift解决的办法是用户自己构造一个subscriber<R>的代理subscriber<T>,把新类型的观测者实现兼容到老类型上去。这个可以参考我之前的代码。
这一点虽然合理,但是仍然比较绕。有没有更加亲民的理解方式?我觉得map做到了,它可以让用户认为,改变的并不是subscriber,而是新构造出了一个observable代理,这就是更便于理解的转换关系。
map代码解析
废话太多,还是看map代码
public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
return lift(new OperatorMap<T, R>(func));
}
lift中最关键的operator实现,就是这个OperatorMap的实现了。
这里RxJava中为了让新老数据类型的转换能够逆转,做了个关键的设计。
注意这个OperatorMap的实现中泛型接口的定义被反转了,在具体接口实现中完成了通用的反转逻辑
public final class OperatorMap<T, R> implements Operator<R, T> {
final Func1<? super T, ? extends R> transformer;
@Override
public Subscriber<? super T> call(final Subscriber<? super R> o) {
return new Subscriber<T>(o) {
...
@Override
public void onNext(T t) {
try {
o.onNext(transformer.call(t));
} catch (Throwable e) {
Exceptions.throwOrReport(e, this, t);
}
}
};
}
}
可以看到,operatormap的核心同样也是实现了一个新的subscriber<R>代理,这个代理里把所有属于subscriber<R> 的观测者实现都映射到了subscriber<T>上,这个待处理的事件的转换过程就是用户要实现的了。
用伪代码来说明下:
r = transformer.call(t)
通过对Func1接口的实现,我们将T类型对象转换为了R类型对象。从这里开始,T类型事件的每一次触发实际都转换为了一个R类型的触发规则,接下来,我们就可以在观测者subscriber<R>中定义触发规则的具体实现了。
从这里开始,用户就不需要考虑代理的问题了,只需要考虑如何把observable事件中emit出的数据转换为新的数据类型就可以。
flatmap
在介绍flatmap的原理之前,首先来看一个场景,如果map将待处理数据类型T转换为一个集合类型List<R> ,那么会出现什么情况?
我很喜欢RxJava 之扔物线《给Android开发者的 RxJava 详解》文章中的例子 ,借用扔物线前辈的这段代码,说明下场景:
/**
* 需要:输出每一个学生选修的课程,对method12的简化
* 嵌套循环的RxJava解决方案
* {@link #method12()}
* Student->ArrayList<Course>
*/
private void method13() {
Observable.from(DataFactory.getData())
.map(new Func1<Student, ArrayList<Course>>() {
@Override
public ArrayList<Course> call(Student student) {
return student.courses;
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<ArrayList<Course>>() {
@Override
public void call(ArrayList<Course> courses) {
for (int i = 0; i < courses.size(); i++) {
Logger.d("观察者:" + courses.get(i).name);
}
}
});
}
看到观测者subscriber处理事件响应时,是把List<Course> 作为一个整体来处理的,observable通过map将每一个Student的数据源转换为一组Course数据源然后交给了subscriber去处理。
通过循环来处理众多的事件对于响应式编程来说是冗繁而且缺乏规范的,我想正是因为List数据源是有厚度的,我们需要用某种方法把这种厚度给抹平,把原本一个整体弹出的数据源想办法转换为单个独立弹出的数据,所以才有了flat——扁平化的map。
先把结论写出来,如果用了flatmap,对比是非常明显的:
/**
* 需要:输出每一个学生选修的课程,对method13的简化
* 嵌套循环的RxJava解决方案
* {@link #method13()}
* Student -> ArrayList<Course> -> Observable<Course> ->
*/
private void method14() {
// Student->Course
Observable.from(DataFactory.getData())
.flatMap(new Func1<Student, Observable<Course>>() {
@Override
public Observable<Course> call(Student student) {
return Observable.from(student.courses);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Course>() {
@Override
public void call(Course course) {
Logger.d("观察者:" + course.name);
}
});
这个subscriber不再需要处理一整块List<Course>数据,因为当委托给它去观测的observable已经变成了Observalbe<Course>,中间的转换细节如下。
flatmap的实现细节
如果仔细看flatmap的诸多重载方法,其核心都是merge方法的使用。谨以以上代码举例,实际使用的是这个flatmap:
public final <R> Observable<R> flatMap(Func1<? super T, ? extends Observable<? extends R>> func) {
if (getClass() == ScalarSynchronousObservable.class) {
return ((ScalarSynchronousObservable<T>)this).scalarFlatMap(func);
}
return merge(map(func));
}
merge方法的作用是将多个observalbe源数据整合到一个observable中。这里func()的实现必须是从数据源类型向被观测者Observalbe类型的转换,简单的说,这里的map映射中做了个二次转换,将原来的数据源T转换为数据源R之后,通过operator创建操作构造了一个相关observalbe。
然后再把这些个observable弹出来的数据统统整合到一个observable中去。