Android架构师必备技能 | 深入Java泛型(下)

  • 泛型的定义与作用
  • 通配符与嵌套
  • 泛型上下边界
  • RxJava中泛型的使用分析

泛型上下边界

上篇讲了泛型通配符分为了无限制通配符,上界通配符,下界通配符 三种。

  • < ? extends E> 是泛型的上边界
  • < ? super E>是泛型的下边界

泛型上下边界的作用

  • 用于指定通配符的范围。
    在实际业务中使用通配符时会遇到很多安全问题如:传入的泛型类没有特定的方法或属性,类型转换错误等等。所以为了防止这些问题的发生,就有了上下边界,用于指定通配符的范围。
一,泛型上限 extends

上限extends指定的类型必须是继承某个类,或者某个接口,即<=,如

? extends Fruit
T extends List

// 容器类(装食物用)
public class Container<T> {
    private T obj;

    public Container(){}

    public Container(T obj){
        this.obj = obj;
    }

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}

示例类:

public class Food<T> {
    private T obj;
    public Food(T obj){
        this.obj = obj;
    }
    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}
public class Fruit<T> extends Food{
    public Fruit(T obj) {
        super(obj);
    }
}
public class Apple<T> extends Fruit{
    public Apple(T obj) {
        super(obj);
    }
}
public class Banana<T> extends Fruit{
    public Banana(T obj) {
        super(obj);
    }
}

public class Meat<T> extends Food{ 
    public Meat(T obj) {
        super(obj);
    }
}
public class Pork<T> extends Meat{
    public Pork(T obj) {
        super(obj);
    }
}

调用:

// 主函数
public static void main(String[] strs) {
        // 水果盘
        Container<Fruit> fruits = new Container<Fruit>();
        // 香蕉篮
        Container<Banana> bananas = new Container<Banana>();
        // 菜盘
        Container<Pork> porks = new Container<Pork>();
        Fruit fruit = new Fruit("水果");
        Banana banana = new Banana("香蕉");
        Apple apple = new Apple("苹果");
        Pork pork = new Pork("土猪肉");
        Meat meat = new Meat("肉");

        // 把洗好的水果装盘
        fruits.setObj(fruit);
        fruits.setObj(apple);
        bananas.setObj(banana);

        Container<? super Meat> container = new Container<>();
        container.setObj(pork);

        Container<? extends Meat> container1= new Container<>();
        //container1.setObj(meat);会报错

        // 把炒好的土猪肉装盘
        porks.setObj(pork);
}
二,泛型下限 super
? super 指定类型

指定类型不能小于操作的类,即指定类型或指定类型的父类…父类的父类最终至Object,且不能为任意父类的其他子类。

// 加菜
public static void addDish(Container<? super Meat> container) {
    // 装土猪肉
    container.setObj(new Pork("土猪肉"));
    // 装牛肉
    container.setObj(new Pork("烤肥牛"));
}

// 主函数
public static void main(String[] strs) {
    // 菜盘 
    Container<Food> foods = new Container<Food>();
    // 专用装肉盘 
    Container<Meat> meats = new Container<Meat>();
    // 水果篮
    Container<Fruit> fruits = new Container<Fruit>();
        
    // 我们吃饭的时候菜吃完,所以我们加菜
    // 厨师准备用盘子装菜
        
    // 用菜盘装菜
    addDish(foods);
    // 用专用装肉盘装菜
    addDish(meats);
    // 但不能用水果篮装菜,一使用编译器就会提示我们异常
//    addDish(fruits);
}

小结:

  • 上限<? extends T>不能往里存,只能往外取 (即:只能get)
    因为编译器只知道容器里的是Fruit或者Fruit的子类,但不知道它具体是什么类型,所以存的时候,无法判断是否要存入的数据的类型与容器种的类型一致,所以会拒绝set操作。
  • 下限<? super T>往外取只能赋值给Object变量,不影响往里存
    因为编译器只知道它是Fruit或者它的父类,这样实际上是放松了类型限制,Fruit的父类一直到Object类型的对象都可以往里存,但是取的时候,就只能当成Object对象使用了。
参考:https://www.jianshu.com/p/0c318bb54502

RxJava中泛型的使用分析

ReactiveX 编程简称 Rx 编程,又叫响应式编程、响应式扩展。

响应式编程:

  • 响应式编程的目标是提供一致的编程接口, 帮助开发者更方便的处理异步数据流,使软件开发更高效、更简洁

Rx 使用观察者模式

  • 创建:Rx 可以方便的创建事件流和数据流
  • 组合:Rx 使用查询式的操作符组合和变换数据流
  • 监听:Rx 可以订阅任何可观察的数据流并执行操作

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

四个基本概念:

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

谈完了响应式的一些东西,简单的说一下泛型的一些概念

泛型分为:

  • 自定义泛型接口 interface Observer
  • 泛型类 class ImplObserver implements Observer
  • 泛型方法 Observer 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 对象,
最后要求这些都是链式操作。

分析一下需求:

现在给一个 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;
}

实现完了是这样子的…

可能你看这点代码会比较不适,我会认真将自己的理解全部写出来

创建被观察者即 ImplObservable.create(new Student()); 这时候我们要把 Student 这个对象存储起来方便之后使用,但是 create 是静态方法
有声明泛型T,但是 ImplObservable 又是被泛型声明的泛型类,在 create 的时候去创建真正的被观察者,并且将 create 方法携带的泛型类型带过去,即被观察者中的泛型来自于create方法的泛型.
而 ImplObservable 的构造方法要求传入一个 T 类型,并且该类中存在一个T t的引用,即保存 create 方法传递过来的实际对象的引用

现在我们搞清楚了一个被观察者中的实际对象(T对象)究竟存储在了哪,一个成员变量T t中

现在我们要想办法把一个存储有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 才真正结束。

参考:https://blog.csdn.net/weixin_47933729/article/details/110678744
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值