1、使用泛型的好处
当没有泛型的时候,Java List的做法是
public interface List extends Collection{
boolean add(Object e);
Object remove(int index);
}
使用泛型之后
public interface List<E> extends Collection<E> {
boolean add(E e);
E remove(int index);
}
由于Java是强类型转换,如果不使用泛型,在运行时可能出现类型转换错误,该错误在编译时无法发现。但是使用了泛型,在编译时会做强类型检查,提高了类型转换的正确率。并且编译时类型擦除,即Java编译源码时,会删除所有的类型参数,替换类型为上下界,如果没有界限,则替换成Object。例如:
public class Zoo<T> {
public boolean putIn(T t) {
return true;
}
}
/************编译之后****************/
public class Zoo {
public boolean putIn(Object t) {
return true;
}
}
带有上下界的编译对比:
public class Zoo<T extends Animal> {
public boolean putIn(T t) {
return true;
}
}
/*****************编译之后*******************/
public class Zoo {
public boolean putIn(Animal t) {
return true;
}
}
2、PECS原则
PECS原则即producer-extends,consumer-super,生产者使用extends,消费者使用super,简单粗暴的例子是Collections.copy
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
//di需要拷贝src的数据给自己,对于他来说,他消费了src的数据,所以消费者角色使用super
ListIterator<? super T> di=dest.listIterator();
//si 需要把自己的数据传递给di,对于他来说,他产生了数据,所以生产者角色使用extends
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
使用其他例子来说明extends & super的用法
private class DataManager<E>{
//以该对象的视角来看,getAll即自己被别人拿走(消费),putAll即自己放入的新的对象进来,自己产生了新的元素
private List<E> list = new ArrayList<>();
public void getAll(List<? super E> out){
//out需要使用list消费,站在out视角,它写入的其他元素,所以适用于写场景
out.addAll(list);
}
public void addAll(List<? extends E> in){
//extends 适用于读场景,因为站在in的视角,它被list读取了
list.addAll(in);
}
}
那么如何正确的使用上下界通配符呢
package com;
import java.util.ArrayList;
import java.util.List;
import javax.swing.plaf.basic.ComboPopup;
import lombok.Getter;
import lombok.ToString;
/**
* @author xiude
* @date 2018/4/1
*/
public class PESCTest {
private static class Group{
}
@ToString
@Getter
private static class Company extends Group{
private String name;
public Company(String name) {
this.name = name;
}
}
public static void main(String[] args) {
//消费者
List<? super Group> listB = new ArrayList<>();
//生产者
//List<? extends Company> listC = new ArrayList<Group>是编译不过的
List<? extends Company> listC = new ArrayList<Company>(){
{
add(new Company("Tmall"));
add(new Company("Taobao"));
}
};
//编译不通过,因为listC被插入了新的元素,属于消费者,消费者只能使用super,但是listC声明成了extends,注定只能读,不能写
//listC.add(new Company("Alipay"));
listB.add(listC.get(0));
listB.add(listC.get(1));
//listB作为消费者,get到的元素z还能赋值给Object
//Company tmall = listB.get(0);
Object tmallObj = listB.get(0);
if(tmallObj instanceof Company){
Company tmall = (Company)tmallObj;
System.out.println("I am "+tmall.getName());
}
listB.stream().forEach(System.out::println);
}
}
使用上界通配符 <? extends Company>
上界通配符,一般用于读取的场景(Company是该类型的上限,如果写是不知道究竟是什么子类)
1、为泛型指定的类型只能是Company类型或者其子类。
2、只能为其列表添加null。
3、get方法获取的值只能赋值给Company类或者其超类。
使用下界通配符 <? super Company>
下界通配符,一般用于写入的场景。(Company是该类型的下限,有下限才能写)
1、为泛型指定的类型必须为Company,或者其超类。
2、可以为其列表添加任意Company类型,或者其子类。
3、get方法获取的类型,只能赋值给Object类型。
这里最后补充一个extends Company vs T extends Company 的区别
public static void println(List<? extends Company> list){
//list.add(new Dept("A")); 编译不通过
/*如果list 传进 List<? extends Company> listD = new ArrayList<Dept>();打印classcom.PESCTest$Dept
如果是 List<? extends Company> listC = new ArrayList<Company>();打印classcom.PESCTest$Company*/
System.out.println(list.get(0).getClass());
}
public static <T extends Company> void print(List<T> list){
list.add((T)new Company("Cainiao"));
System.out.println(list.get(0).getClass()); //结果class com.PESCTest$Company
}
- 通配符(? extends Company):可以进行获取操作,但是不能写入(set(? extends Number)不支持);
- 类型参数(T extends Company): 获取和写入都支持,并且可以声明引用和作为返回值;例如