RxJava出现在我们的视线已经很久了,我自己也有阅读过非常多的文章,谈不上精通,但是勉强称得上会一些简单的使用,近日总是对这种响应式的编程,对RxJava魂牵梦绕,深刻的感觉到自己对泛型的认识,理解不到位,对RxJava的核心,观察者模式有很多的不理解,导致在编码或者说思想上达不到自己想要的效果
So,想着既然要学RxJava,自己对泛型的认识又不够,就决定深入研究一下RxJava的源码对泛型的使用,在探究源码的过程中去理解泛型,去使用泛型,在泛型的学习中理解掌握RxJava,算是一种互补吧
再此默认大家已经会简单使用RxJava,并且对RxJava的操作符(Operation)有一些了解
什么叫做响应式编程,什么叫做观察者模式,什么又叫做事件,什么叫做消费,我谈一下我自己的理解,如有不恰当之处,请大家指正(轻喷),这篇文章我也会长期更新下去,每次都会涉及到RxJava的操作符和自己去编写这些操作符的实现
响应式编程:
与我们传统编码(函数式编程)不一样,传统编码是做完这件事之后做另外一件事,给人的感觉都是单线程的,可能会开新线程去
处理耗时操作,在处理完成之后通过回调去处理之后的事情
而响应式编程提供给我们的是一种不一样的思想,在响应式编程的世界中一切执行流程都是基于事件的,已事件为驱动
观察者模式:
观察者模式是这样子的,我先举个例子看大家能不能理解
老师在讲台上讲课,而所有的学生都会观察着老师的一举一动,而老师每产生一个事件(比如说在黑板上写下一串公式),则对应着所有的学生都观察到了老师的这一举动,自己则在自己的笔记本中记录,大脑中进行思考.而老师却不关心自己的学生对这一举动做什么事.
好了,例子就是这样的,我们来分析一下这个例子跟观察者模式有个什么关系?
这个例子中,老师可以产生事件,学生观察着老师,而老师在产生事件之后咳嗽一下,通知所有的学生,我刚才做了什么事,你们应该也需要做点自己的事情
而这就产生了几个概念,观察者,被观察者,事件,事件的处理与消费
被观察者中存在观察者的引用,即教师知道自己要通知的学生都有谁
被观察者在产生事件之后通知观察者,即教师产生事件之后通知每一位观察着自己的学生
RxJava是对观察者模式的一种高级运用,或者说是一种升级,他把观察者模式具体化,更加明确了各个对象之间的关系
四个基本概念:
Observable (可观察者,即被观察者)、
Observer (观察者)、
subscribe (订阅)、事件。
Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。
谈完了响应式的一些东西,我觉得既然要讨论学习泛型的使用,我们就把泛型的一些概念也揪出来瞅一下
泛型分为:
1 : 自定义泛型接口 interface Observer<T>
2 : 泛型类 class ImplObserver<T> implements Observer<T>
3 : 泛型方法 <T> Observer<T> call(T t)
说一下泛型的作用域
如果将泛型声明放在泛型接口,泛型类上,则该泛型在该类中就是确定的了,如果将泛型声明放在了泛型方法上,则该泛型只在该方法中有效,如果泛型方法上声明的泛型类型和类或接口中声明的泛型一致,则会在该方法中隐藏类或接口上的泛型
贴个代码看一下
将泛型声明放在接口
public interface Observable<T> {
public T call();
}
将泛型声明放在方法
public interface Observable2 {
<T> T call(T t);
}
泛型声明在接口或类上,则类或接口中的方法均可使用T类型
public class ImplObservable<T> implements Observable<T>{
@Override
public T call() {
// TODO Auto-generated method stub
return null;
}
}
泛型声明在方法上,则除去该声明有T泛型的方法之外,其他方法不识别T类型
public class ImplObservable2 implements Observable2{
@Override
public <T> T call(T t) {
// TODO Auto-generated method stub
return null;
}
}
public static void main(String[] args) {
//将泛型声明在接口上或声明在类上
Observable<Student> observer = new ImplObservable<Student>();
Student student = observer.call();
//将泛型声明在方法上
ImplObserver2 Observable2 = new ImplObservable2();
Student student2 = observer2.call(new Student());
}
大概了解一下泛型的作用域和泛型的类型之后,我们现在有这么一个需求
我给你一个对象,你能够观察着该对象,即一个观察者中存在着该对象的引用,并且将该观察者返回给我
我刚开始是这么想的,我们看一下有没有什么问题
public class ImplObservable<T> implements Observable<T>{
T t;
public ImplObservable(T t){
this.t = t;
}
}
看代码的话好像确实也没什么问题,我把泛型的声明放在了类上,那我这个类中都是可以识别T类型的,那我在创建对象的时候传入T好像也没什么不对,一样完成了需求,我们回到创建该对象的main方法中去看一看,创建方法变成了这样
ImplObservable<Student> observer = new ImplObservable<>(new Student());
如果我把<>删除掉,则编译器会给我们这样一个警告
Type safety: The expression of type ImplObservable needs unchecked conversion to conform to ImplObservable<Student>
类型不安全?怎么会不安全?并没有报错啊..
事情是这样的,在ImplObserver中,我们将泛型声明放在了类上,在该类中都可以识别T类型了,但是,构造方法接受一个T类型,如果你在创建该对象的时候,没有向该类声明T类型究竟属于哪种类型,就直接传递了一个实际类型过去,问题就像这样,教室接受所有类型过来,可能是教师,也可能是学生,但是,你在创建该教室的时候,你对教室接受的类型进行了限制,但是你又没有通知教室说教室准确的要接受哪种类型的对象,这就会造成泛型不安全
我去翻了翻Rxjava的源码,他将Observable这个对象的构造函数的访问权限降低了,不在他包下都不可以创建这个对象,但是他提供了一个create方法去创建,我们也来模仿一下
public class ImplObservable<T> implements Observable<T>{
T t;
private ImplObservable(T t){
this.t = t;
}
public static <T> Observable<T> create(T t) {
return new ImplObservable<T>(t);
}
}
创建方法变成了这样
Observable<Student> create = ImplObservable.create(new Student());
这样我们在使用ImplObserver的时候就没有对这个类的泛型进行明确说明,而是在create方法中进行了声明,怎么声明的? 这里面还有点门道,我们将create方法定义成了静态方法,并且在该方法上声明了T类型,这样该方法的T类型就会隐藏掉类上的T类型,但是,我们的create方法做了这么一件事,将静态方法的泛型,传递给了ImplObservable类上的泛型,并且返回创建好的ImplObservable泛型对象,此处的泛型类型为create方法声明的泛型类型
是不是有点晕了?我当时也是晕的不行,迷糊过来之后也就那样吧..如果有迷糊的朋友在下方评论吧,指出你的问题,我们一起讨论
现在来考虑Rxjava写代码舒服的原因,全链式,全链式啊有木有,一条道走到黑,就是在不停的调用调用调用,不需要我们去考虑返回的对象是什么对象,只需要进行一系列操作就可以了,因为泛型已经帮助我们做了太多太多.
链式?哇,链式调用好像是很牛逼的,我们也来实现一下.
先说一下需求:
现在我给你一个student对象,你把这个对象给我通过某种规则给转换成teacher对象,并且!
你要给我返回的观察者不在是观察学生了,而是,你刚才转换成的teacher对象,并且!
我要求这些都是链式操作,起码我看起来比较舒服,写起来也比较开心!
说实话我是在学习泛型,研究Rxjava,我为啥非得给自己找不自在,提出的需求比较恶心就算了,还并且,俩并且,完成功能不就行了吗?追求那么点的链式可能会给我的工作,我的业余时间带来什么呢?
好了,我们来分析一下需求:
现在给一个student对象,要返回一个观察着student的观察者,我们通过上面的代码可以这样创建
ImplObservable.create(new Student());
现在要把这个学生通过某种规则转换成teacher
做一个接口回调,传递学生类型进去,返回老师类型,但是这俩类型不明确,应该用泛型
我们模仿Rxjava的命名,也叫作Func1,
public interface Func1<T,R> {
R call(T t);
}
接口做好了,我们现在要在Observer中去定义一个方法,将T类型转换成R类型,为了保持和Rxjava的一致,我们也叫作map
并且该方法要接受一种规则,一种能够将T转成R的规则
方法声明也有了
<R> Observer<R> map(Func1<T,R> fun1);
我们要在ImplObserver中去实现该方法了
@Override
public <R> Observer<R> map(Func1<T, R> fun1) {
// TODO Auto-generated method stub
Observer<R> ob = ImplObservable.create(fun1.call(t));
return ob;
}
实现完了是这样子的...
可能你看这点代码会比较恶心,甚至会吐..
先喝杯水,起来晃悠一下,放松一会,希望你待会能打起十二分精神来读接下来的一丁点篇幅
我会认真将自己的理解全部写出来.
1:
创建被观察者即ImplObservable.create(new Student());这时候我们要把Student这个对象存储起来方便之后使用,但是create是静态方法,
有声明泛型T,但是ImplObservable又是被泛型声明的泛型类,在create的时候去创建真正的被观察者,并且将create方法携带的泛型类型带过去,即被观察者中的泛型来自于create方法的泛型.
而ImplObservable的构造方法要求传入一个T类型,并且该类中存在一个T t的引用,即保存create方法传递过来的实际对象的引用
现在我们搞清楚了一个被观察者中的实际对象(T对象)究竟存储在了哪,一个成员变量T t中
2:
现在我们要想办法把一个存储有t对象的被观察者转换成一个存储有另外一个t对象的被观察者,我们提供一个map操作,代表类型的转换操作
map要怎么实现是我们现在重点思考的问题
既然ImplObservable中可以存储t对象,一个ImplObservable对应一个T类型,也就意味着一个ImplObservable存储的这个t对象的类型已经确定,
那么我们要怎么把一个T对象转换成R对象,转换规则是怎么样的
public interface Func1<T,R> {
R call(T t);
}
定义这么一个接口,接受一个T类型,返回一个R类型,在call方法中编写转换规则.
那么map方法就必然要接受一个接口了,即转换规则
我们暂且这样定义map方法
<R> Observable<R> map(Func1<T,R> fun1);
既然map方法也有了转换的规则
map的实现就这样了
@Override
public <R> Observable<R> map(Func1<T, R> fun1) {
Observable<R> ob = ImplObservable.create(fun1.call(t));
return ob;
}
至于为什么这么做?
现在我们知道ImplObservable.create方法接受一个T类型,并且把T类型存储到当前对象中去,叫做t,这里是没毛病的
我们来回想一下Func1这个接口的泛型声明,接受T,返回R.
call方法接受T,返回R
这就意味着我们的ImplObservable.create方法接受的就是一个R类型!!!
并且ob对象中存储的那个T t类型,实际上就应该是R r对象,即Teacher对象
这时候我们返回了ob,一个存储有R(teacher)对象的被观察者
至此,student转换为teacher才真正结束.
好像是有点晕,好吧,回头我画个图在说一下....
好了放松一下吧...确实比较恶心,也有点绕口,烧脑,但是想通了也就是那么一回事...
现在再来定义一个操作符,我们就结束今天这篇文章了
需求是这样的
我需要在被观察者的执行过程中改一下被观察者中存在的对象的属性
并且不能破坏链式
我只是修改属性,我要的还是该被观察者
分析一下:
一个接口回调,需要把被观察者保存的对象给传递回来,返回的结果不关心,即(void)
代码实现:
//声明下一步做的事
Observable<T> doOnNext(Action<T> action);
//定义泛型接口
public interface Action<T> {
void callAction(T t);
}
实现doOnNext方法
@Override
public Observable<T> next(Action<T> action) {
action.callAction(t);
return this;
}
解释一下
当前被观察者中已经存在T对象的引用即t,只需要将t回调过去,在外部类中进行修改,
但是被观察者是不改变的,直接返回this就可以了.
最后上一下测试代码
public static void main(String[] args) {
Student student = new Student();
System.out.println("创建好student : " + student);
final Teacher teacher = new Teacher();
System.out.println("创建好teacher : " + teacher);
ImplObservable.create(student)
.map(new Func1<Student, Teacher>() {
@Override
public Teacher call(Student t) {
// TODO Auto-generated method stub
System.out.println("student hashcode : " + t);
System.out.println("teacher hashcode : " + teacher);
return teacher;
}
})
.doOnNext(new Action<Teacher>() {
@Override
public void callAction(Teacher t) {
// TODO Auto-generated method stub
System.out.println("teacher hashcode2 : " + t);
}
});
}
输出结果
创建好student : com.lxkj.learn.Student@95cfbe
创建好teacher : com.lxkj.learn.Teacher@1950198
student hashcode : com.lxkj.learn.Student@95cfbe
teacher hashcode : com.lxkj.learn.Teacher@1950198
teacher hashcode2 : com.lxkj.learn.Teacher@1950198
在RxJava的世界里,你可以把所有的被观察者想成一条河流,既然是河流,他就可以被过滤,拦截,转换,变换,修饰,处理,甚至于合并等一系列操作,河流在流动的过程中观察者是不会处理的,只有在河流抵达了终点,即被观察者订阅了观察者之后,观察者才会对最终的这股河流进行处理.
RxJava之所以强大,是因为他对河流在流动过程中提供了太多太多的操作符,我们能想到的操作,在RxJava中基本都有某种操作符来处理,而RxJava不太赞成我们自己去定义操作符,因为定义这些操作符的逻辑确实太绕了,就像上面我们自己定义map操作符一样,真的是非常难受,一不小心可能就会造成一连串的错误
这篇文章的重点从RxJava的四个概念开始,到结束,全都是我和同学一点点分析总结出来的,跟源码比起来搓一万倍,
只能说跟大家一块儿感悟一下Rxjava的魅力和他的执行流程,对自己理解Rxjava也算是一点帮助吧,
最后想说的是,泛型这玩意,是真厉害...
以后没事的话就去研究几个操作符,对自己理解泛型理解Rx都是有很大帮助的.我也非常乐意分享这些心得,也希望大家能批评文章中的错误,我会认真吸取经验反哺大家的..