深入java泛型(四rxjava中深入理解泛型)(代码片段)

持续学习刻意练习 持续学习刻意练习     2022-11-28     349

关键词:

第一章深入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泛型

泛型是什么一说到泛型,大伙肯定不会陌生,我们代码里面有很多类似这样的语句:List<String>list=newArrayList<>();ArrayList就是个泛型类,我们通过设定不同的类型,可以往集合里面存储不同类型的数据类型(而且只能存储... 查看详情

java中泛型的深入理解(代码片段)

文章目录泛型深入泛型基本介绍自定义泛型类自定义泛型方法自定义泛型接口泛型通配符和上下限泛型深入泛型基本介绍泛型的概述:泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛... 查看详情

java5泛型深入研究

Java5泛型深入研究 上接《Java泛型的理解与等价实现》,这个仅仅是泛型的入门。有博友反映泛型很复杂,难以掌握。鉴于此,写一片续集。 实际上泛型可以用得很复杂,复杂到编写代码的人员自己也难以看懂。这往往是... 查看详情

深入理解java泛型擦除机制(代码片段)

深入理解Java泛型擦除机制我们都知道Java中的泛型可以在编译期对类型检查,避免类型强制转化带来的问题,保证代码的健壮性。不同语言对泛型的支持也不一样,Java中的泛型类型在编译期会擦除,下面一个例子... 查看详情

java泛型深入理解(代码片段)

泛型之前 在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如... 查看详情

大数据必学java基础(五十五):泛型深入了解(代码片段)

文章目录泛型深入了解一、引入1、什么是泛型(Generic)2、没有泛型的时候使用集合3、JDK1.5以后开始使用泛型,集合中使用泛型4、泛型总结二、自定义泛型结构1、泛型类,泛型接口2、泛型方法3、泛型参数存在... 查看详情

深入理解java泛型协变逆变泛型通配符自限定(代码片段)

禁止转载重写了之前博客写的泛型相关内容,全部整合到这一篇文章里了,把坑都填了,后续不再纠结这些问题了。本文深度总结了函数式思想、泛型对在Java中的应用,解答了许多比较难的问题。纯函数协变逆变泛型通配符PECS... 查看详情

android开发之深入理解泛型extends和super的区别(代码片段)

摘要:什么是泛型?什么是擦除边界?什么是上界限定或下界限定(子类型限定或超类型限定)?什么是类型安全?泛型extends关和super关键字结合通配符?使用的区别,两种泛型在实际Android开发中有... 查看详情

深入java泛型(三泛型的上下边界)(代码片段)

第一章深入Java泛型三、泛型的上下边界3.1<?extendsE>:上界通配符(UpperBoundsWildcards)3.2<?superE>:下界通配符(LowerBoundsWildcards)3.3上下通配符的副作用三、泛型的上下边界?extendE是泛型的上边界࿰... 查看详情

深入java泛型一泛型的作用与定义(代码片段)

第一章深入Java泛型一、泛型的作用与定义1.1泛型的作用1.2泛型的定义1.3泛型擦除一、泛型的作用与定义1.1泛型的作用使用泛型能写出更加灵活通用的代码。泛型的设计主要参照了C++的模板,旨在能让你写出更加通用化&... 查看详情

深入java泛型二通配符与嵌套(代码片段)

第一章深入Java泛型二、通配符与嵌套2.1通配符2.2泛型嵌套2.3通配符和泛型方法二、通配符与嵌套2.1通配符?:表示类型不确定,只能用于声明变量或者形参上,不能用在创建泛型类、泛型方法和接口上。publicstaticvoidmai... 查看详情

android开发之深入理解泛型extends和super的区别(代码片段)

摘要:什么是泛型?什么是擦除边界?什么是上界限定或下界限定(子类型限定或超类型限定)?什么是类型安全?泛型extends关和super关键字结合通配符?使用的区别,两种泛型在实际Android开发中有... 查看详情

深入理解c#泛型:new与where关键字全解析(代码片段)

C#泛型中new和where是重要的关键字,它们都可以用于约束泛型类型参数的限制;它们都用于提高代码的安全性和可用性,它们的作用在很大程度上提高了代码的可读性和可维护性。在这篇文章中,我们将一起了解泛型中的new和where... 查看详情

深入理解dart中的类型系统和泛型(代码片段)

Dart中的类型系统不够严格,这当然和它的历史原因有关。在Dart最开始诞生之初,它的定位是一门像JavaScript一样的动态语言,动态语言的类型系统是比较松散的,所以在Dart类型也是可选的。然后动态语言类型系统松散对开发者... 查看详情

深入java泛型(三泛型的上下边界)(代码片段)

第一章深入Java泛型三、泛型的上下边界3.1<?extendsE>:上界通配符(UpperBoundsWildcards)3.2<?superE>:下界通配符(LowerBoundsWildcards)3.3上下通配符的副作用三、泛型的上下边界?extendE是泛型的上边界࿰... 查看详情

c#深入了解泛型

本文是根据网上&书本总结来的。 1.介绍泛型程序设计是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时(instantiate)作为参数指明这些类型。... 查看详情

深入理解java数组

目录  1.简介  2.声明数组  3.创建数组  4.访问数组  5.数组的引用  6.泛型和数组  7.多维数组  8.Arrays类  9.小结  10.参考资料??本文已归档到:「javaco 查看详情

深入理解threadlocal

  SUN公司早在JDK1.2的时候就为我们提供了java.lang.ThreadLocal,低版本的JDK所提供的get()返回的是Object对象,需要强制类型转换,使用起来不方便,而在JDK1.5引入了泛型,在一定程度地简化ThreadLocal的使用。  我们知道在spring容器... 查看详情