泛型程序
一、泛型类
public class Pair<T>{
private T first;
private T second;
public Pair(){first = null;second = null;}
public Pair(T first,T second){this.first = first;this.second = second;}
public T getFirst(){return first;}
public T getSecond(){return second;}
public void setFirst(T newValue){first = newValue;}
public void serSecond(T newValue){second = newValue;}
}
二、泛型方法
class ArrayAlg{
public static <T> T getMeddle(T... a){
return a[a.length/2]
}
}
三、 类型变量的限定
方法内部有需要创建对象调用方法的,因为泛型类型变量T可以是任何对象,不一定有有该方法(compareTo),所以需要限定。
比如下方代码中,需要将类型变量T限定为Comparable接口:
public static <T extends Comparable> T min(T[] a)
;
用关键字extends表示限定为绑定类型的子类型,可以是接口,也可以是类;
限定类型用&分隔,可以有多个接口,但至多只能有一个类作为限定,并且类必须是限定列表中的第一个。T extends Comparable & Serializable
class ArrayAlg{
public static <T extends Compareble> Pair<T> minmax(T[] a){
if(a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for(int i = 1; i < a.length;i++){
if(min.compareTo(a[i]) > 0) min = a[i];
if(max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min,max);
}
}
四、 泛型代码和虚拟机
虚拟机没有泛型类型对象,所有对象都属于普通类。
泛型类型,会被擦除类型变量,并被替换为限定类型(没有限定类型的变量用Object)。
- 翻译泛型表达式
Pair<Employee> buddies =...;
Emloyee buddy = buddies.getFirst();
擦除类型变量后,getFirst方法返回类型变为Object类型;
编译器会自动在字节码中插入强制转换,转换为相应的泛型类型。
程序调用泛型方法时,会调用原始方法,然后强制转换为泛型类型。
-
翻译泛型方法
泛型方法被擦除后,只剩下限定类型。
泛型方法:public static <T extends Comparable> T min(T[] a)
擦除后:public static Comparable min(Comparable[] a)
-
桥方法(保持多态性)
class DateInterval extends Pair<Date>{
/**
* 一个日期区间是一堆Date对象(first,second)
* 覆盖Pair的setSecond方法确保second永远不小于first
*
*/
public void setSecond(Date second){
if(second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
}
类型擦除后
class DateInterval extends Pair{//after erasure
public void setSecond(Date second){...}
}
父类Pair中也有public void setSecond(Objec second)
方法
泛型方法类型擦除后,与多态发送冲突,编译器子类中会生成桥方法:
public void setSecond(Object second){ setSecond( (Date) second);}
避免了这个冲突。
//DateInterval类的覆盖getSecond方法
class DateInterval extends Pair<Date>{
public Date getSecond(){return (Date) super.getSecond().clone();}
}
类型擦除后,有两个getSecond方法
Date getSecond()
//DateInterval
Object getSecond()
//Pair
在java代码中,具有相同参数类型的两个方法是不合法的,但是在虚拟机中,用参数类型和返回类型确定一个方法,编译器可能产生仅返回类型不同的方法字节码,虚拟机可以正确处理。
五、调用没有泛型的遗留代码
有时候可能会调用没有泛型的遗留代码,传入带有泛型值,有可能产生强制类型转换的异常,编译时会报警告。确认后,可以利用标注使之消失。
@SuppressWarnings("unchecked")
Dictionary<Integer,Components> labelTable = slider.getLabelTable();
六、约束与局限性
- 不能使用基本类型实例化类型参数,
没有Pair<double>
,只有Pair<Double>
;类型擦除后,Pair类泛型类型会变成Object类型的域,Object不能存储基本类型。
-
运行时类型查询只适用于原始类型
-
instanceof或涉及泛型类型的强制转换都会产生编译器警告:
Pair<String> p = (Pair<String>)a;//WARNING
-
getClass方法返回的也是原始类型:
Pair<String> StringPair =...;
Pair<Employee> employeePair =...;
if(StringPair.getClass() == employeePair.getClass())//ture;返回的都是Pair.class
-
不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10]
;//ERROR -
实例化参数化类型数组会报错:
类型擦除后,table的类型是Pair[],可以转换为Object[],数组会记住它的类型元素(Pair[]),如果视图存储为其他元素会,产生一个ArrayStoreException异常。
Object[] objarray = table;
objarray[0] = "Hello";//Error
objarray[0] = new Pair<Employee>();//正常
/*此处可以通过数组存储检查,但是会导致类型错误。
泛型类型的擦除导致 数组存储检查机制失效
所以不允许创建参数化类型的数组*/
-
可以声明
Pair<String>[]
,但是不能用new Pair<String>[10]
初始化这个变量。可以通过声明通配符的类型数组,然后进行类型转换:
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
结果会不安全,可能会产生ClassCastException异常。
例如:如果
table[0]
存储一个Pair<Employee>
,对table[0].getFirst()
调用一个返回String方法,会产生异常。 -
如果需要收集参数化类型对象,可以用:
ArrayList:ArrayList<Pair<String>>
。 -
varargs警告
-
个数可变方法实际上是一个数组,由于java不支持泛型类型的数组,会违法规则。
但是对于varargs情况,规则有所放松,只会产生一个警告。
-
采用标注
@SuppressWarnings("unchecked")
、@SafeVarargs
来抑制警告。可能会产生异常。
@SafeVarargs static <E> E[] array(E... array){return array;}
Pair<String> table = array(pair1,pair2);
Object[] objarray = table;
objarray[0] = new Pair<Employee>();
/*可以顺利运行,不会出现ArrayStoreExcep异常
(数组存储只会检查擦除的类型),但是处理table[0]时会得到一个异常。
*/
- 不能实例化类型变量
- 不能使用
new T(...),new T[...],T.class
这一的表达式。类型擦除后,T变成Object,本意肯定是不希望调用 new Object()。可以使用反射调用Class.newInstance方法来构造泛型对象。
public static <T> Pair<T> makePair(Class<T> c1){
//Class类本身是泛型,
try{
return new Pair<>(cl.newInstance(),c1.newInstance())
}catch(Eception e){
return null;
}
}
Pair<String> p = Pair.makePair(String.class);//makePair可以推断出pair的类型
- 不能构造一个泛型数组
public static <T extends Comparable> T[] minmax(T[] a) {
T[] mm= new T[2];//类型擦除会让这个方法永远构造Object[2]数组
...
}
public static <T extends Comparable> T[] minmax(T... a){
T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(),2);
...
}
-
泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。
public class Singleton<T>{
private static T singleInstance;//Error
public static T getSingleInstance(){//Error
if(singleInstance == null)
return singleInstance;
}
}
- 不能抛出或捕获泛型类的实例
- 注意擦除后的冲突
泛型类不能定义与父类中同名的同参数的泛型方法。
public class Pair<T>{
public boolean equals(T value){...}
}
/*
Pair<String> 会有两个equals方法:
boolean equals(T) //defined in Pair<T>
boolean equals(Object) //inheited from Object
但是boolean equals(T)方法擦除后就是boolean equals(Object),与Object,equals方法产生冲突。
*/
要想支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类型,而这两个接口是同一个接口的不同参数化。
七、泛型类的继承规则
Pair<Manager>
不是Pair<Employee>
子类
要考虑是否可能产生ClassCastException异常。
八、通配符类型
-
带有子类的通配符
Pair<? extends Employee>
-
通配符的超类限定
Pair<? super Manager
(Manager对象及其子类) -
Pair<? extends Comparable<? super T>> T min(T[] a)
-
无限定通配符
Pair<?>
-
通配符的捕获
//交换一个pair元素的方法
public static swap(Pair<?> p){//Error
? t = p.getFirst(); //Error
p.setFirst(p.getSecond);
p.setSecond(T);
}
//True
public static swap(Pair<?> p){
swapHelper(p);
}
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond);
p.setSecond(T);
}
九、反射和泛型
Class<T>的方法:
-
T newInstance()
返回默认构造器构造的一个新实例
-
T cast(Object obj)
如果obj 为null或有可能转换成类型T,则返回obj;否则抛出BadCastException异常
-
T[] getEnumConstants()
如果T是枚举类型,则返回所有值组成的数组,
-
Class<? super T > getSuperclass()
返回这个类的超类。如果T不是一个类或者Object类,则返回null
-
Constructor<T> getConstructor(Class... parameterTypes)
-
Constructor<T> getDeclaredConstructor(Class... parameterTypes)
获得公有的构造器,或带有给定参数类型的构造器。
Constructor<T>的方法:
-
T newInstance(Object... paramters)
返回用指定参数构造的新实例