- 泛型的定义与作用
- 通配符与嵌套
- 泛型上下边界
- 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<>();
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);
}
小结:
- 上限
<? 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);
}
public class ImplObservable<T> implements Observable<T>{
@Override
public T call() {
return null;
}
}
public class ImplObservable2 implements Observable2{
@Override
public <T> T call(T t) {
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) {
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
|