欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅谈Java泛型中的的区别

程序员文章站 2022-05-23 15:21:53
...
引入
再说这个之前,先来看一段代码:
假设有这么几个类及其继承关系,后面的例子也用这几个类作为基础示范
class People {
     //人
}
class Man extends People {
     //男人
}
class Woman extends People {
     //女人
}
class Boy extends Man {
     //男孩
}

可以看到,man是people的子类,那么:
 
   List<People> list= new ArrayList<Man>();
     
是否可以编译通过?
浅谈Java泛型中的<? extends E>和<? super E>的区别
浅谈Java泛型中的<? extends E>和<? super E>的区别

很明显,编译的时候报错了。man是people的子类,ArrayList是List的子类,但并不代表List<Man>是List<People>的子类。所以便有了有限通配符
<? extends E>
<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限,比如:
List<? extends People> list_1 = null;
表示集合中的元素上限是People,即只能是People或People的子类,所以下面的赋值是合法的,编译时候不会报错:
浅谈Java泛型中的<? extends E>和<? super E>的区别
但不能是其父类,否则编译时候就报错了:
浅谈Java泛型中的<? extends E>和<? super E>的区别
接下来对其读写数据进行了解:
1、读
不管给该list_1如何赋值,可以保证的是里面存放的一定是People或People的子类,编译器可以确定获取的是People类型,所以可以直接从该集合中读取到People类型。即读取是允许的。
浅谈Java泛型中的<? extends E>和<? super E>的区别
2、写
浅谈Java泛型中的<? extends E>和<? super E>的区别
People、Man、Woman都是People类或其子类,但是这里却是编译错误的。原因是? extends People>仅仅告诉编译器该集合元素类型上限是People,这里编译器并不能确定具体的类型,即它可能实际指向了Man,但是你却add一个Woman类型,所以这里编译器不允许这么做。
<? super E>
<? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限,比如:
List<? super Man> list_4 = null;
该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类,如下:
浅谈Java泛型中的<? extends E>和<? super E>的区别
接下来对其读写数据进行了解:
1、读
允许从该集合获取元素,但是无法保证里面存放的是Man或者是Woman,唯一可以确定的是存放的是Object或其子类,而无法确定具体的类型。
浅谈Java泛型中的<? extends E>和<? super E>的区别
这样都没错,但是实际用的时候还是要注意,像这样获取Woman可能导致异常。
2、写
可以确定的是集合中的元素一定是Man或Man的子类,所以添加Man或Boy都是正确的,但是不能添加非Man的子类:
浅谈Java泛型中的<? extends E>和<? super E>的区别

使用场景
很多时候都是用它来当作方法中的形参。
这里先了解下PECS法则
1、PECS
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>
2、例子
这里使用网上常见的例子水果来说明,有如下关系:
浅谈Java泛型中的<? extends E>和<? super E>的区别
假设此时有个水果供应商Produce,
class Produce<E> {
     public void produce(List<E> list) {
           for (E e : list) {
                //生产...
                System.out.println("批量生产...");
           }
     }
}

它主要销售水果
Producer<Pear> p = new Produce<>();
List<Pear> pears = new ArrayList<Pear>();
p.produce(pears);
这样并没有什么问题。但是万一他突然想换成销售苹果了,此时:
浅谈Java泛型中的<? extends E>和<? super E>的区别
这样就会发现,编译并不能通过,因为List<E>已经在初始化时确定为Pear了,而不再兼容Appler类型,即使你最开始使用的是Produce<Fruit>,即方法produce的参数list为List<Fruit>,虽然Apple和Pear是Fruit的子类,但是由上面的引入知识知道,List<Fruit>并不是List<Apple>的父类,即这样也是行不通的,所以这里就需要使用List<? extends E> list了。
修改后如下:
class Producer<E> {
     public void produce(List<? extends E> list) {
           for (E e : list) { //利用<? extends E>读取的特性
                //生产...
           }
         System.out.println("批量生产完成...");
     }
}

此时只要供应商new的时候为Fruit,则生产的货物只要为Fruit或其子类即可,所以Pear和Apple都可通过。如下:
浅谈Java泛型中的<? extends E>和<? super E>的区别
接着举一个消费者的例子(可能例子举得不是很好)
//消费者
class Consumer<E> {
     public E consume(List<E> list) {
           E e = list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
每次消费者都从一个list中消费一个。加入有一个红苹果消费者:
浅谈Java泛型中的<? extends E>和<? super E>的区别
这里是没什么问题的,但是红苹果也是苹果,如果这样呢:
浅谈Java泛型中的<? extends E>和<? super E>的区别
这时候,<? super E>派上用场了。
//消费者
class Consumer<E> {
     public E consume(List<? super E> list) {
           E e = (E) list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
此时再按刚才的操作:
浅谈Java泛型中的<? extends E>和<? super E>的区别
编译并不会出问题了。

其实,在java提供的许多类库里就有用到了,比如Collections的静态方法copy:
浅谈Java泛型中的<? extends E>和<? super E>的区别
为了保证在list复制过程中类型的安全,限制了原list的上限,保证了目标数组的下限。