在泛型类出现之前,主要通过继承来实现功能,比如ArrayList类只维护一个Object引用的数组,这样的问题在于:
1、从数组中获取一个值时必须进行强制类型转换,2、可以向数组中添加任意类的对象(影响程序的可读性和安全性)
泛型提供了更好的解决方案:类型参数,ArrayList有一个类型参数来指定元素的类型:ArrayList<String> files=new ArrayList<String>();
Java 7以后可以更进一步简化:ArrayList<String> files = new ArrayList<>();
一个泛型类就是具有一个或多个类型变量的类,
public class Pair<T> {
private T first;
private T second;
public Pair(){}
public Pair(T a,T b){
this.first=a;
this.second=b;
}
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;
}
}
Pair pair=new Pair();如果这样没有显式的声明类型,Pair默认为Object,可以往里面添任意的东西
public static <T> T func(T[] a){
return a[0];
}//普通函数也可以使用泛型
在调用时,可以这样
Base.<Integer>func(a);
Base.func(a);
有时需要对类型变量加以约束,否则无法采用Comparable
public static <T extends Comparable> T min(T[] a){
if(a.length==0||a==null){
return null;
}else{
T min=a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0){
min=a[i];
}
}
return min;
}
}
Comparable接口本身也是一个泛型类型,但注意这里用的是extends而不是implements,在这个地方,不论是子类或是接口,都用extends
public static <T extends Comparable> T min(T[] a){
if(a.length==0||a==null){
return null;
}else{
T min=a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0){
min=a[i];
}
}
return min;
}
}
T extends A &b 进行多个限定,至多只有一个类,且必须放在第一个位置
在Java虚拟机中没有泛型类型,所有的对象都属于普通类,定义泛型类型时会自动提供一个原始类型,原始类型就是擦除类型变量并替换为限定类型(没有限定类型则替换为Object),成为一个普通的类,Pair<T>的原始类型就是将所有的T变成Object,
原始类型用第一个限定的类型变量来替换(没有则Object),当泛型声明为具体类型进行操作时:
Pair<Employee> buddies=....
Employee buddy=buddies.getFirst();
编译器1、调用原始的方法 2、对原始类型Object进行强制类型转换
泛型方法中也进行类型擦除:
public static <T extends Comparable> T min( T[] a) --------> public static Comparable min (Comparable[] a)
class DateInterval extends Pair <Date>并重写了关于Date的set函数,问题是在方法擦除以后,从Pair继承来的是Object的方法,擦除变量和多态产生冲突
Pair擦除以后的方法:
public void setSecond(Object second) {
this.second = second;
}
DateInterval擦除以后的方法:
public void setSecond(Date second) {
。。。具体里面什么书上没说
}
编译器在DateInterval类中产生桥方法:
public void setSecond(Object second){
setSecond((Date)second);
}
如果也重写getSecond方法:
public Date getSecond(){
return (Date)super.getSecond().clone();
}
就会出现:两个方法,从签名上来看,只有返回值不同(一个是DateInterval的Date,一个是Pair的Object),这在Java里是无法区分的,但是在虚拟机里确实可以区分的。
总之:
1、Java中没有泛型,只有普通方法和类
2、所有的类型参数都用他们的限定类型来替换
3、桥方法被合成,来保证多态
泛型的限制:
1、类型参数只能是普通类,不能是8种基本类型
2、对于类型的判断,虚拟机对象总有一个非泛型类型,所有的查询只产生原始类型
Pair<Date> d=new DateInterval(2, new Date(), new Date());
if(d instanceof Pair){
System.out.println(1111);//ok
}else{
System.out.println(2222);
}
Pair<Date> d=new DateInterval(2, new Date(), new Date());
if(d instanceof Pair<?>){
System.out.println(1111);//ok
}else{
System.out.println(2222);
}
if(d instanceof Pair<Date>)//编译不通过
Pair<String>[] table=new Pair<String>[10]; //ERROR
只有一种安全而高效的方法:使用ArrayList: ArrayList<Pair<String>> a=new ArrayList();
4、不能实例化类型变量
也就是不能在类中进行 new T();
5、不能在静态方法和静态域中使用类型变量T
6、无论S与T是什么关系,Pair<S>和Pair<T>一般没有什么联系
也就是无法将Pair<Manager>赋给 Pair<Employee>
public static void print(Pair<Employee> p){
Employee e1=p.getFirst();
Employee e2=p.getSecond();
e1.print();
}
解决方法是:使用通配符类型
public static void print(Pair<? extends Employee> p){
Employee e1=p.getFirst();
Employee e2=p.getSecond();
e1.print();
}
这里,Pair<Manager>是Pair <?extends Employee>的子类型
通配符不仅可以限定从谁继承,还可以指定超类: ? super Manager
直观地讲:带有超类限定的通配符可以向泛型对象写入,带子类限定的通配符可以从泛型对象读取