上集讲到Java的泛型(Java必知必会之(二)---泛型(上))的原理和基本用法,本文将会讲到的是泛型的进阶知识:通配符、PECS原则和类型擦除。
1.类型通配符
类型通配符:额...说白了就是一个?。当确定集合是某种数据类型的时候,你可以写List,而当不确定集合是哪一种类型的时候,就可以写成List<?>。 当然,也可以用其它的字母来表示,如下常用的通配符有: E — Element,常用在java Collection里,如:List,Iterator,Set K,V — Key,Value,代表Map的键值对 N — Number,数字 T — Type,类型,如String,Integer等等 这些只是一些常用的约定写法,并不需要特别记住,我们可以随意用26位字母去表示。至于?和字母的区别就在于:在Java集合框架中,对于参数值是未知类型(即使用“?”通配符)的容器类,由于编译器无法预知其具体类型,所以只能读取,不能增删,但NULL是例外。 如下面的代码就会编译出错
List<?> list=new ArrayList<String>();
list.add("1");//error
list.add(NULL);//ok
复制代码
list定义的是无边界通配符,往一个未知类型的l中加入类型为String的数据,编译器就会报错,但加入null就不会报错。 通常,通配符有三种:
- 无边界通配符:<?> 就是?, 如 Set<?>、List<?>、Map<?,?>,作用是让泛型能接受未知类型的数据。
List<?> l=new ArrayList();
Set<?> s=new TreeSet();
复制代码
- 子类限定通配符:<? extends E> 表示能够接受指定类及其子类类型的数据。要求<?>的数据类型必须为E或者E的子类。
public static void c(List<? extends String> l){
System.out.println(l.get(0));
}
public static void main(String[] args) {
List<Integer> l1=new ArrayList();
l1.add(1);
List<String> l2=new ArrayList();
l2.add("2");
//error:The method c(List<? extends String>) in the type TestT is not applicable for the arguments (List<Integer>)
TestT.c(l1);
TestT.c(l2);
}
复制代码
- 超类限定通配符:<? super E> 表示能够接受指定类及其父类类型的数据.<?>必须为E或者E的父类。
public static void superD(List<? super Integer> s){
System.out.println(s.get(0));
}
List<String> lString=new ArrayList();
lString.add("2");
List<Object> lObject=new ArrayList();
lObject.add("2");
//error:The method superD(List<? super Integer>) in the type TestT is not applicable for the arguments (List<String>)
TestT.superD(lString);
TestT.superD(lObject);
复制代码
2.PECS原则
PECS全文为“Producer Extends, Consumer Super”,意思是作为作为生产者时使用extends,作为消费者时使用super。 啥玩意儿???听不懂 没关系 来看下面的例子:
List<? extends Ineteger> fe = new ArrayList();
List<? super Ineteger> fs=new ArrayList();
//The method add(capture#5-of ? extends Object) in the type List<capture#5-of ? extends Object> is not applicable for the arguments (String)
//生产者操作
fe.add(1);
fs.add(1);
//消费者操作
for (Integer s : fe) {
}
for (Integer s : fs) {
}
复制代码
可以看出,fe.add()作为生产者的动作往集合中添加String类型的数据时,编译器会报错:因为java中不允许不确定的类型加入集合,作为Integer的子类,此时编译器并不知道fe被add进去的具体是什么子类型,因此就会报错(因为子类可能有着和父类不一样的形态,如果不限定规则,随便往里添加各种不同的子类,那在读取list里的内容时,就会出错,因为Java干脆把规则限定在前面)。而在进行消费者的操作时,fs在遍历数据时,由于数据类型为Integer或者Integer的父类,因此,<?>并不知道具体是哪一种类型,所以编译器会报错。因为PECS最大的原则就是无论是生产操作还是消费操作,必须让编译器知道具体操作的类型是什么,否则不好意思,不给过!! 由上面的例子我们可以整理出一下规则:
- 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
- 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
- 如果既要存又要取,那么就不要使用任何通配符
3.类型擦除:
JDK1.5引入了泛型,旨在减少类型强制转换的操作,加强参数类型的安全,但是泛型的作用域在编译阶段,编译完成之后,所有泛型参数类型都会被清除,这就是类型擦除。 下面来看一段代码:
List<String> listString=new ArrayList();
List<Integer> listInteger=new ArrayList();
System.out.println(listString.getClass());
//输出:class java.util.ArrayList
System.out.println(listInteger.getClass());
//输出:class java.util.ArrayList
System.out.println(listInteger.equals(listString));
//输出:true
复制代码
上面的代码得出以下结论:
- listString、listInteger并不是ArrayList的子类,它们都是ArrayList
- listString、listInteger都指向同一个内存地址
由此可见,泛型的存在只是在编译阶段,在运行阶段它们的类型已经被擦除,而泛型也不存在父类子类这种说法。