深入Java泛型(四RxJava中深入理解泛型)

Posted 持续学习刻意练习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入Java泛型(四RxJava中深入理解泛型)相关的知识,希望对你有一定的参考价值。

第一章深入Java泛型

四、RxJava中深入理解泛型

4.1 响应式编程

与我们传统编码(函数式编程)不一样,传统编码是做完这件事之后做另外一件事,给人的感觉都是单线程的,可能会开新线程去处理耗时操作,在处理完成之后通过回调去处理之后的事情。

而响应式编程提供给我们的是一种不一样的思想,在响应式编程的世界中一切执行流程都是基于事件的,以事件为驱动。

4.2 观察者模式

观察者模式是这样子的,举个例子:

老师在讲台上讲课,而所有的学生都会观察着老师的一举一动,而老师每产生一个事件(比如说在黑板上写下一串公式),则对应着所有的学生都观察到了老师的这一举动,自己则在自己的笔记本中记录,大脑中进行思考,而老师却不关心自己的学生对这一举动做什么事。

我们来分析以下这个例子跟观察者模式有什么关系?

这个例子中,老师可以产生事件,学生观察着老师,而老师在产生事件之后咳嗽一下,通知所有的学生,我刚才做了什么事,你们应该也需要做点自己的事情。

这就产生了几个概念:观察者、被观察者、事件、事件的处理与消费。

被观察者中存在观察者的引用,即老师知道自己要通知的学生都有谁。

被观察者在产生事件之后通知观察者,即老师产生事件之后通知每一位观察着自己的学生。

4.3 RxJava 是对观察者模式的一种高级运用,或者说是一种升级,它把观察者模式具体化,更加明确了各个对象之间的关系

四个基本概念:

  • Observable:可观察者、即被观察者
  • Observer:观察者
  • Subscribe:订阅
  • 事件

Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。

关于泛型再补充下一些知识点:

泛型分为

  1. 自定义泛型接口
interface Observer<T>
  1. 泛型类
class ImplObserver<T> implements Observer<T>
  1. 泛型方法
<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() 
        return null;
    


// 泛型声明在方法上,则除去该声明有 T 泛型的方法之外,其他方法不识别 T 类型
public class ImpleObservable2 implements Observable2 

    @Override
    public <T> T call(T t) 
        return null;
    


public static main(String[] args) 
    // 将泛型声明在接口或声明在类上
    Observable<Student> observable = new ImplObservable<Student>();
    Student student = observable.call();
    // 将泛型声明在方法上
    ImplObservable2 observable2 = new ImplObservable2();
    Student student2 = observable2.call(new Student());


现在有一个需求,给你一个对象,你能够观察着该对象,即一个观察者中存在着该对象的引用,并且将该观察者返回给我。

我刚开始是这么想的,看下有没有声明问题。

public class ImplObservable<T> implements Observable<T> 
    T t;

    public ImpleObservable(T t) 
        this.t = t;
    

看代码的话好像确实也没有什么问题,把泛型的声明放在了类上,那这个类中都是可以识别 T 类型的,那在创建对象的时候传入 T 好像也没什么不对,一样完成了需求,我们回到创建该对象的 main 方法中去看一看,创建方法变成了这样。

public static main(String[] args) 
    ImplObservable<Student> observable = new ImplObservable<>(new Student());

如果把 <> 删除掉,则编译器会给我们这样一个警告:

Raw use of parameterized class 'ImplObservable'
Unchecked assignment: 'ImplObservable' to 'ImplObservable<Student>'
Unchecked call to 'ImplObservable(T)' as a member of raw type 'ImplObservable'

类型不安全?怎么会不安全了?并没有报错啊。。。

事情是这样的,在 ImplObservable 中,我们将泛型声明放在了类上,在该类中都可以识别 T 类型了,但是构造方法接口一个 T 类型,如果你在创建该对象的时候,没有向该类声明 T 类型究竟属于哪种类型,就直接传递了一个实际类型过去,问题就像这样,教室接受所有类型过来,可能是教师,也可能是学生,但是,你在创建该教室的时候,你对教室接受的类型进行了限制,但是你又没有通知教室说教室准确的要接受哪种类型的对象,这就会造成泛型不安全。

你去翻一番 RxJava 的源码,会发现它将 Observable 这个对象的构造函数的访问权限降低了,不在他包下都不可以创建这个对象,但是他提供了一个 create 方法去创建,我们也来模仿一下:

public class ImplObservable<T> implements Observable<T> 

    T t;

    private ImpleObservable(T t) 
        this.t = t;
    

    public static <T> Observable<T> create(T t) 
        return new ImplObservable<T>(t);
    

创建方法变成了这样:

Observable<Student> observable = ImplObservable.create(new Student());

这样我们在使用 ImplObservable 的时候就没有对这个类的泛型进行明确说明,而是在 create 方法中进行了声明的,怎么声明的?将 create 方法定义成了静态方法,并且在该方法上声明了 T 类型,这样该方法的 T 类型就会隐藏掉类上的 T 类型,create 方法中将静态方法的泛型传递给了 ImplObservable 类上的泛型,并且返回创建好的 ImplObservable 泛型对象,此处的泛型类型为 create 方法声明的泛型类型。

RxJava 之所以能用全链式的方式写代码,不需要我们去考虑返回的对象是什么对象,只需要进行一系列的操作就可以,这是因为泛型已经帮助我们做了太多太多。那我们也来实现下链式调用。

先说下需求,给你一个 student 对象,你把这个对象通过某种规则给转换成 teacher 对象,并且返回的观察者是观察 teacher 对象,而不是 student 对象,以上操作要求都是链式操作。

我们来分析下需求,现在给一个 student 对象,要返回一个观察着 student 的观察者,我们通过上面的代码可以这样创建:

ImplObservable.create(new Student());

现在要把这个 student 通过某种规则转换成 teacher,做一个接口回调,传递 student 类进去,返回 teacher 类,但是这两类型不明确,应该用泛型。我们模仿 RxJava 的命名,也叫作 Func1:

public interface Func1<T, R> 
    R call(T t);

接口做好了,就要在 Observable 中去定义一个方法,将 T 类型转换成 R 类型,为了保持和 RxJava 的一致,我们也叫做 map,并且该方法要接受一种规则,一种能够将 T 转换成 R 的规则。

public interface Observable<T> 
    <R> Observable<R> map(Func1<T, R> func1);

接下来就是在 ImplObservable 中去实现该方法了:

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);
    

    @Override
    public <R> Observable<R> map(Func1<T, R> func1) 
        Observable<R> observable = ImplObservable.create(func1.call(t));
        return observable;
    

实现完了是这样,可能第一次看的时候不太能理解,也正常,我也是这样,所以我们再来详细分析一下。

  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 方法。

public interface Observable<T> 
    <R> Observable<R> map(Func1<T, R> func1);

既然 map 方法也有了转换的规则,map 的实现就这样了

public class ImplObservable<T> implements Observable<T> 

      ……

    @Override
    public <R> Observable<R> map(Func1<T, R> func1) 
        Observable<R> observable = ImplObservable.create(func1.call(t));
        return observable;
    

为什么这么做呢?现在我们知道 ImplObservable.create 方法接受一个 T 类型,并且把 T 类型存储到当前对象去,叫做 t。而 Func1 这个接口中的 call 方法接受 T 返回 R,这就意味着 ImplObservable.create 方法接受的就是一个 R 类型。并且 observable 对象中存储的那个 T t 类型实际上就应该是 R r 对象,即 Teacher 对象,这时候我们返回 Observable observable,一个存储有 R(Teacher) 对象的被观察者。

至此,student 转换为 teacher 才真正结束。

现在我们再来定义一个操作符,需求是这样的,我需要在被观察者的执行过程中改一下呗观察者中存在的对象的属性,并且不能破坏链式,我只是修改属性,我要的还是被观察者。

分析一下需求,一个接口回调,需要把被观察者保存的对象给传递回来,返回的结果不关心,即(void)。

代码实现:

首先定义泛型接口:

public interface Action<T> 
    void callAction(T t);

声明下一步做的事情(doNext):

public interface Observable<T> 

    <R> Observable<R> map(Func1<T, R> func1);

    Observable<T> doNext(Action<T> action);

转换规则同上:

public interface Func1<T, R> 
    R call(T t);

实现 doNext 方法:

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);
    

    @Override
    public <R> Observable<R> map(Func1<T, R> func1) 
        Observable<R> observable = ImplObservable.create(func1.call(t));
        return observable;
    

    @Override
    public Observable<T> doNext(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);
   ImplObservable3.create(student)
        .map(new Func1<Student, Teacher>() 


            @Override
            public Teacher call(Student student) 
                System.out.println("student hashcode : " + student);
                System.out.println("teacher hashcode : " + teacher);
                return teacher;
            
        )
        .doNext(new Action<Teacher>() 


            @Override
            public void callAction(Teacher teacher) 
                System.out.println("teacher hashcode2 : " + teacher);
            
        );

输出的结果如下:

创建好 student : com.example.demo.Student@9a63ffa
创建好 teacher : com.example.demo.Teacher@8a399ab
student hashcode : com.example.demo.Student@9a63ffa
teacher hashcode : com.example.demo.Teacher@8a399ab
teacher hashcode2 : com.example.demo.Teacher@8a399ab

以上是关于深入Java泛型(四RxJava中深入理解泛型)的主要内容,如果未能解决你的问题,请参考以下文章

Java中泛型的深入理解

Java泛型深入理解

深入理解Java泛型

深入理解Java之泛型

深入理解 Java 泛型擦除机制

夯实Java基础系列13:深入理解Java中的泛型