浅谈Java泛型中的

引入
再说这个之前,先来看一段代码:
假设有这么几个类及其继承关系,后面的例子也用这几个类作为基础示范
class People {
     //人
}
class Man extends People {
     //男人
}
class Woman extends People {
     //女人
}
class Boy extends Man {
     //男孩
}

可以看到,man是people的子类,那么:
 
   List list= new ArrayList();
     
是否可以编译通过?


很明显,编译的时候报错了。man是people的子类,ArrayList是List的子类,但并不代表List是List的子类。所以便有了 有限通配符
是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限,比如:
List list_1 = null;
表示集合中的元素上限是People,即只能是People或People的子类,所以下面的赋值是合法的,编译时候不会报错:

但不能是其父类,否则编译时候就报错了:

接下来对其读写数据进行了解:
1、读
不管给该list_1如何赋值,可以保证的是里面存放的一定是People或People的子类,编译器可以确定获取的是People类型,所以可以直接从该集合中读取到People类型。即读取是允许的。

2、写

People、Man、Woman都是People类或其子类,但是这里却是编译错误的。原因是? extends People>仅仅告诉编译器该集合元素类型上限是People,这里编译器并不能确定具体的类型,即它可能实际指向了Man,但是你却add一个Woman类型,所以这里编译器不允许这么做。
是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限,比如:
List list_4 = null;
该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类,如下:

接下来对其读写数据进行了解:
1、读
允许从该集合获取元素,但是无法保证里面存放的是Man或者是Woman,唯一可以确定的是存放的是Object或其子类,而无法确定具体的类型。

这样都没错,但是实际用的时候还是要注意,像这样获取Woman可能导致异常。
2、写
可以确定的是集合中的元素一定是Man或Man的子类,所以添加Man或Boy都是正确的,但是不能添加非Man的子类:


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

它主要销售水果
Producer p = new Produce<>();
List pears = new ArrayList();
p.produce(pears);
这样并没有什么问题。但是万一他突然想换成销售苹果了,此时:

这样就会发现,编译并不能通过,因为List已经在初始化时确定为Pear了,而不再兼容Appler类型,即使你最开始使用的是Produce,即方法produce的参数list为List,虽然Apple和Pear是Fruit的子类,但是由上面的引入知识知道,List并不是List的父类,即这样也是行不通的,所以这里就需要使用List list了。
修改后如下:
class Producer {
     public void produce(List list) {
           for (E e : list) { //利用读取的特性
                //生产...
           }
         System.out.println("批量生产完成...");
     }
}

此时只要供应商new的时候为Fruit,则生产的货物只要为Fruit或其子类即可,所以Pear和Apple都可通过。如下:

接着举一个消费者的例子(可能例子举得不是很好)
//消费者
class Consumer {
     public E consume(List list) {
           E e = list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
每次消费者都从一个list中消费一个。加入有一个红苹果消费者:

这里是没什么问题的,但是红苹果也是苹果,如果这样呢:
浅谈Java泛型中的_第2张图片
这时候,派上用场了。
//消费者
class Consumer {
     public E consume(List list) {
           E e = (E) list.get(0); //模拟消费一个(感觉用队列比较合适)
           return e;
     }
}
此时再按刚才的操作:
浅谈Java泛型中的_第3张图片
编译并不会出问题了。

其实,在java提供的许多类库里就有用到了,比如Collections的静态方法copy:
浅谈Java泛型中的_第4张图片
为了保证在list复制过程中类型的安全,限制了原list的上限,保证了目标数组的下限。

参考链接: https://www.cnblogs.com/wangmingshun/p/5389341.html
                    http://blog.csdn.net/asdfsadfasdfsa/article/details/52794573

你可能感兴趣的:(java)