Java泛型的使用
泛型
Java中的泛型定义
泛型是 JDK1.5 的一个新特性,泛型就是将类型参数化,其在编译时才确定具体的参数。 这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
好处
没有泛型的话,要容纳不同的类型,就用Object。但是这样就没有了类型检查。
List list = new ArrayList();
list.add("orzlinux.cn");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1); //ClassCastException
根据《Java 编程思想》中的描述,泛型出现的动机在于:有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。
好处如下:
类型安全
编译器就能检查出因Java类型不正确导致的异常。
消除强制类型转换
如ArrayList存放还是用Object数组,强制转换它内部做了。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { transient Object[] elementData; E elementData(int index) { return (E) elementData[index]; } 。。。 }
LinkedList就是直接Node结点了:
private static class Node<E> { E item; Node<E> next; Node<E> prev; } public E get(int index) { checkElementIndex(index); return node(index).item; }
潜在的性能收益
所有的代码都在编译器中完成,生成的代码跟不使用泛型时所写的代码几乎一致。
泛型原理
类型擦除:使用泛型的时候加上类型参数,编译器的编译的时候会去掉类型参数。
泛型只存在于编译阶段,而不存在于运行阶段。
例如:
package cn.orzlinux.skjava.base;
public class GenericsDemo<T> {
private T num;
}
IDEA编译后反编译还会出现泛型:
package cn.orzlinux.skjava.base;
// 原因是在编译.java文件时虽然会擦除泛型,但是泛型信息会以
// 注释的形式记录下来,所有有的反编译工具可以正常展示泛型信息。
public class GenericsDemo<T> {
private T num;
public GenericsDemo() {
}
}
严格来讲,应该是:
public class GenericsDemo {
private Object num;
public GenericsDemo() {
}
}
大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extends
和super
语法的有界类型,如:
public class Caculate<T extends String> {
private T num;
}
这种情况的泛型类型,num 会被替换为 String 而不再是 Object。
泛型中的通配符
限定通配符:一种是<? extends T>
它通过确保类型必须是T或者T的子类来设定类型的上界,另一种是<? super T>
它通过确保类型必须是T或者T的父类来设定类型的下界。
非限定通配符 ?:可以用任意类型来替代。如List<?>
的意思是这个集合是一个可以持有任意类型的集合,它可以是List<A>
,也可以是List<B>
,或者List<C>
等等。
List<?>
则表示其中所包含的元素类型是不确定,其中可能包含的是String
,也可能是Integer
. 如果它包含了String
的话,往里面添加Integer
类型的元素就是错误的。作为对比,我们可以给一个List<Object>
添加String
元素,也可以添加Integer
类型的元素, 因为它们都是Object
的子类。
运行期间泛型是表现不出来的。
ArrayList<String> a = new ArrayList<>();
ArrayList<Integer> b = new ArrayList<>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); // true
什么是PECS?
要使用的类:苹果和水果类。
class Fruit {}
class Apple extends Fruit {}
PECS指Producer Extends,Consumer Super
。 如果你是想遍历,并对每一项元素操作时,此时这个集合是生产者(生产元素),应该使用 Collection<? extends Thing>
。 如果你是想添加元素到collection
中去,那么此时集合是消费者(消费元素)应该使用Collection<? super Thing>
。
总结一下就是:
LinkedList<? super Fruit>
说明里面要存放的是Fruit或者它爹们,但是爹太多不确定,确定的是啥?Fruit自己及它的儿子们(可以转为Fruit变量)肯定是可以的。所以实际存只能存Fruit及其子孙。那要读呢?里面存的明面上还是Fruit或者它爹们,非要读的话只能赋给Object。
LinkedList<? extends Fruit>
说明里面要存放的是Fruit或者它崽们,但是崽太多不确定,确定的是啥?啥都不确定。所以实际不能存(硬要说的话可以存null,因为null 可以表示任何类型。)。那要读呢?里面存的明面上还是Fruit或者它崽们,非要读的话可以直接赋给Fruit。
问题来了:
问题一:List<? extends Fruit>
我都不能存我读个屁啊。
不能存但是初始化的时候可以给一个List,如下:
LinkedList<? super Fruit>
说是能放Fruit的爹们,但是也没见add也加不进去它爹啊。
同上:
那晃悠这一圈干嘛呢?直接apples读不就完了?为的是让list能够接受不同的List<XXX>
,但是又有一定的限制。