Java核心技术读书笔记8-泛型程序设计
1.泛型概述
泛型设计在JDK5被引入,它提供了一个类型参数,提前指示某种数据结构将操纵的数据类型,即可以在编译时检测出非法类型。
List
泛型的设计可以使程序有更强的可读性和安全性。
类型参数不能是基本数据类型
2.泛型类
一个泛型类就是有一个或多个泛型变量的类。泛型参数是一种类似于占位符的功能,在调用时指定。
public class Pair<T> { //指定了一个类型参数T,若有多个类型参数用逗号隔开如:<T,U>
private T first; //类型参数可以指定域和局部变量
private T second;
public Pair() {}
public Pair(T first, T second) { //作为方法入参
this.first = first;
this.second = second;
}
public T getFirst() {return first;} //作为方法返回值
public void setFirst(T first) {this.first = first;}
public T getSecond() {return second;}
public void setSecond(T second) {this.second = second;}
}
//使用泛型构造一个类,对泛型参数传入一个具体类型,则所有同泛型参数位置将被替换为该类型
public class Test {
public static void main(String[] args) {
Pair<String> pair = new Pair<>();
pair.setFirst("1");
pair.setSecond("2");
pair.getSecond();
}
}
所以,泛型类可以看做普通类的工厂。
2.泛型方法
类似于泛型类,类型参数在方法修饰符后面定义,作用域为该方法。泛型方法也可以在非泛型类中定义。
public static <T> T[] testMethod(T... t){
System.out.println(t.getClass().getName());
return t;
}
public static void main(String[] args) {
Test.<Double>testMethod(521d); //编译器可以根据参数推断出T的类型括号可以省略,但若不省略则要在方法前加上调用者
testMethod(521d);
testMethod(100, 512d, 2f, 10000L); //对于这种情况,编译器会认为T为这几参数类型的共同类型即Number
testMethod(new ArrayList<>(), "1234", 1234); //Serializable
}

3.类型变量的限定
如果我们相对类型变量T t1做一些操作。比如调用Comparable接口的compareTo方法与其他T类型变量作比较,如何确保作为通用类型的T实现了Comparable接口呢?这就需要对类型变量作出限定:
这样就要求传入的类型变量所属类必须实现Comparable接口。若有多个限定则使用&隔开:
<T extends A & B>
4.泛型代码与虚拟机
虚拟机没有泛型对象,泛型类型都会被擦除,若泛型类型有限定则替换为第一个限定类型,否则替换为Object,对于返回值被擦除为Object的,使用时系统会自动进行强转:
<T extends Comparable & AutoCloseable> 替换为Comparable类型 //若使用close方法,系统会在使用时做强转。
pair.getSecond(); 替换为(String)pair.getSecond();
类型擦除带来的问题
-由于擦除后得到的结果只是一个普通类或普通方法。同时在类型擦除时可能会合成桥方法来保持多态。
因此,不能使用a instanceof Pair
语句或者做强制类型转换Pair p = (Pair )a,毕竟泛型会被擦除,虚拟机中没有泛型相关的类型。
new Pair().getClass == new Pair ().getClass //返回true
-同时创建使用参数化的类型数组(使用变量则没问题Pair
-不能实例化类型变量,如new T(..)、new T[..]、T.class
-不能构造泛型数组:T[]
-不能在静态域或方法中引用类型变量
-泛型类不能扩展Throwable,所以更不可能捕获或抛出泛型类对象。
-注意类型擦除后的冲突
@Override
public boolean equals(Object obj) {return super.equals(obj);}
public boolean equals(T obj) {return super.equals(obj);} //类型擦除后会引起冲突
5.泛型类型的继承规则
泛型类之间与参数类型之间的关系无关。

数组协变,超类数组变量可以引用子类数组变量Employee[] e = new Manager[10]; 但之后只能向数组传入Manager类型变量(类似于多态)。
而原始类型变量可以引用一个泛型类型构建的对象。但可能会产生类型错误。
Pair
Pair p = mp;
p.setFirst(new File(..)); //只有警告,不会报错。
泛型类型与对应的原始类型没有类间的关系,如ArrayList

6.有界通配符
通配符可以实现参数类型的限定继承关系问题,由于泛型类之间没有继承关系(Array
上界通配符:<? extends A> //类型参数必须是A或A的子类,同时<? extends A>是<A>的子类。
下界通配符:<? super A> //类型参数必须是A或A的超类,同时<? super A>是<A>的超类。


6.1 有界通配符的特性:
· 通配符后只能有一个引用:<? extends A & B>非法
· 通配符只能出现在形参和引用(List<? extends Employee> list = new ArrayList<>();)中,不允许出现在泛型定义中,即泛型接口、类、方法。
· 通配符可以作为参数说明,但对使用通配符的变量的数据存取会有一定限制。带有超类限定的通配符可以向泛型对象写入,带有子类限定的通配符可以从泛型对象读取。
怎么理解这句话?
首先要明白向上转型即,Employee e = new Manager(),超类类型变量可以引用子类类型对象。
试想,对于方法:aMethod(List<? extends Employee> list)其参数,数组列表list。我们考虑为list添加值,我能只能为其添加其元素类型的同类型或更低的元素类型,但是其元素类型是一个以Employee为上界的不确定类型,这无法办到(其实也可以用Object得到)。
同时对于aMethod(List<? super Employee> list)的list,我们想取出他的值,那么就要用一个层级更高的类型接收其元素,但很可惜,其元素类型是没有上界的,这同样无法办到。
相反,List<? extends Employee> list,为元素类型指定了一个上界,我们可以很简单的用Employee类型(及Employee的父类)变量接收list中的元素,即带有子类限定的通配符可以从泛型对象读取。
而,List<? super Employee> list,为元素类型指定了一个下界,我们将任何低于Employee类层级的类型元素加入到集合中而进行向上转型,即带有超类限定的通配符可以向泛型对象写入。
public static void printElement(List<? extends Employee> list){
for (Employee e : list) { //指定上界后,可以用任何高于上界类型的元素接收值
System.out.println(e.toString());
}
}
public static void addElementTolist(List<? super Employee> list ,Employee e){
list.add(e); //指定下界后,可以将任何低于下界类型的元素添加到集合中
}
public static void main(String[] args) {
List<Employee> list = new ArrayList<>();
addElementTolist(list, new Employee(1000, "王工"));
addElementTolist(list, new Manager(10000, "李总"));
printElement(list);
}
7.无界通配符
无解通配符相当于上界为Object的有界通配符。即List<?>等价于List<? extends Object>。因此其合法操作只有读操作。
8.捕获通配符
因为我们对于<?>,不能用?作为一种参数类型,所以有需要取值的时该怎么办呢?如:
public static void swapElements(List<?> list){
Object o = list.get(0);
list.set(0, list.get(1)); //无法设置值
list.set(1, o); //同样无法设置值
}
这时候就需要用到具体泛型T去捕获实际类型了
public static void swapElements(List<?> list){
swapHelper(list);
}
public static <E> void swapHelper(List<E> list){ //使用泛型T捕获值
E e = list.get(0);
list.set(0, list.get(1));
list.set(1, e);
}
public static void main(String[] args) {
List<Employee> list = new ArrayList<>();
addElementTolist(list, new Employee(1000, "王工"));
addElementTolist(list, new Manager(10000, "李总"));
swapElements(list);
System.out.println(list);
}
9.反射与泛型
Class类是泛型的。例如,String.class实际上是一个Class<String>类的对象。类型参数十分有用,它允许Class<T>方法的返回类型更加具有针对性。比如对于参数Class<T> c。调用c.newInstance()实际上就是T.class.newInstance()。


10.虚拟机中的泛型信息。
Java可以通过反射获得类与方法的泛型相关信息,如T。但是不会知道对于特定的对象和方法调用,如何即使类型参数,如具体的<String>
在反射中,提供了Type接口,包括:
· Class类,描述具体类型
· TypeVariable接口,描述类型变量(T extends Comparable<? super T>)
· WildcardType接口,描述通配符(? super T)
· ParameterizedType接口,描述泛型类或接口类型(Comparable<? super T>)
· GenericArrayType接口,描述泛型数组(T[])




浙公网安备 33010602011771号