首页 > 代码库 > 泛型程序设计
泛型程序设计
1.0为什么要使用泛型
泛型程序设计让不同类型对象只需要使用同一个类创建。大大减少代码量。还能减少异常的出现。
在还没有泛型时,ArrayList是这样的
public class ArrayList{ public Object get(int i){........} public void add(Object o){.............} private Object[] elementData;}
当我们创建ArrayList对象,往里面存储对象时都会被转型为Object
ArrayList a=new ArrayList();a.add(1);a.add("r");
a对象不会对元素进行检查类型,当储存不希望的类型的元素也不会出错。
当我们想提取元素时必须转型。
String b=(String)a.get(1);
如果提取非String元素却转型String将出错。。。
泛型将会杜绝此类错误
ArrayList<String> a=new ArrayList<String>();
当我们储存元素时编译器将会检查是否为String类型,提取也不要用转型。。 而且增加可读性,知道此ArrayList对象存储的是String类型元素。
2.0简单的泛型类定义
一个简单的存储和提取两个变量的类
public class Pair <T>{ public Pair(){} public Pair(T f,T s){ first=f; second=s; } private T first; private T second; public T getFirst(){return first;} public T getSecond(){return second;} public void setFirst(T f){first=f;} public void setSecond(T s){second=s;}}
泛型支持多类型变量:<T,E>,按顺序第一个域,第二个域。
字母含义:T 类型。E 元素类型。K 表关键字类型。V 表的值类型。
例:静态方法minmax遍历数组计算出最小值和最大值返回一个Pair对象(存储到Pair中),使用类中方法输出。
public class Test121 { public static void main(String[]args) { String words[]={"ab","ab","abc","abcd"}; Pair<String> a=minmax(words); System.out.println(a.getFirst()); System.out.println(a.getSecond()); } public static Pair<String> minmax(String[]x) { if(x==null||x.length==0)return null; String min =x[0]; String max =x[0]; for(int i=1;i<x.length;i++) { if(min.compareTo(x[i])>0)min=x[i]; if(max.compareTo(x[i])<0)max=x[i]; } return new Pair<String>(min,max); }}
2.3泛型方法
思路与泛型类一样,可以在普通类定义泛型方法
public class Test{ public static <T>T getMiddle(T[]a){ //计算出数组中的中间数 return a[a.lenght/2] } }
String a=Test.<String>getMiddle(new String[]{"uu","oo"});//调用方法 :大多数情况下<String>是可以省略的,因为编译器通过参数的Sting数组判断出T为String类型。
泛型方法的类型变量也是不限数量的<T,E,V>
一个不太严谨的例子,编译器无法判断返回类型:
doule middle =Test.getMiddle(3.14,465.2,45);
编译无法编译产生错误信息:"found:java.;ang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>,required:double"
编译器把参数打包为Integer和Double,找到共同父类Number和Comparable(可以赋给这两类型),必须把参数类型都改为double。
2.4类型变量的限定
顾名思义,就是对T类型进行限制。例:
public static<T extends Comparable>T min(T[]a){ ..... }
T必须继承Comparable接口,注意:不用implements使用了extends表明也可以继承普通类
public static<T extends Comparable & Serializable >T min(T[]a){ ..... }
一个T可以实现多个接口但只能继承一个类,如果继承了类,这个类必须写在第一位置
public static <T extends Comparable>Pair<T> minmax(T[]x) { if(x==null||x.length==0)return null; T min =x[0]; T max =x[0]; for(int i=1;i<x.length;i++) { if(min.compareTo(x[i])>0)min=x[i]; if(max.compareTo(x[i])<0)max=x[i]; } return new Pair<T>(min,max); }
minmax方法中类型变量T必须继承Comparable接口,注意,写了<T extends Comparable> ,即使方法中其它所有T都改为String也不会出错,总结:当出现<T>只是告诉编译器此方法或类,出现的T将会被替换,但是不强制一定要有T ,一定要有几个T。。。解放思维
5.0泛型代码和虚拟机
虚拟机不区分泛型,编译器让泛型类和泛型方法感觉和普通的一样让虚拟机运行。java5.0之前的虚拟机不能运行sun的编译器编译有泛型代码的文件。
定义泛型类型会提供一个原始类型:删去类型参数的泛型类型名。 擦除类型变量替换成限定类型,无限定的为Object。
Pair<T>的原始类型
public class Pair{ pirvate Object firts; .......................}
Pair<T extends Comparable & Serializable>的原始类型
public class Pair{ pirvate Comparable firts; .......................}
注意:<T extends Comparable & Serializable>如果Serializable在前那么原始类型就是它。必要时原始类型Comparable可以强转Serializable,所以为了提高效率,一般把标签接口放在尾部。
5.1翻译泛型表达式
Pair<Employee> a=new Pair<Employee>; Employee b=a.getFirst();
定义泛型对象时,泛型类型被擦除为原始类型。调用方法时会有两条虚拟机命令:
!对原始方法getFirst()调用。
!对返回的Object型强转为Employee型。
5.2翻译泛型方法
泛型方法也会类型擦除
public static <T extends Comparable>T min(T[]a) public static Comparable min(T[]a)//<span style="font-family: Arial, Helvetica, sans-serif;">擦除后 </span>
方法擦除导致的两个复杂问题
class Dates extends Pari<Date second>{ public void setSecond(Date second){ if(second.compareTo(getFirst())>=0) super.setSecond(second); //比原来的second大就替换,小就不变 }}
setSecond(Date second)方法是重写了从Pair类继承的方法。实际情况是这样的吗?
实际是pair类中只存在setSecond(Object second)方法,Datas类继承了它。和setSecond(Date second)方法实际是重载关系。
看看我们使用多态的情况:
Dates a=new Dates();Pair<Date> b=a;a.setSecond(Date second);
根据多态特性这里应该调用setSecond(Object second),结果调用了setSecond(Date second)(泛型擦除和多态性冲突)。为什么会这样呢?
因为编译器在Dates类增加了一个桥方法:
public void setSecond(Object second){setSecond(Date) second;}//调用A转到B
如果覆盖的是get方法呢
public Date getSecond(){ return ......;}
那编译器会在Date类中写一个Object getSecond()的桥方法,奇怪的是那么Date类就有两个同名同参数方法(不合法),我们不能这么写,但是编译器可以。
虚拟机通过参数,返回值类型区分方法,所以还是能区分开的。
桥方法----------带有泛型的方法改写都会被编译器改造成 桥方法
约束和局限性
12.6.1不能用基本类型实例化类型参数
类型参数的原始类型是Object,调用类型变量时会强转成限定类型,基本类型不能强转。
6.2运行时类型查询只适应于原始类型
虚拟机是不认泛型的,在它眼里都是普通类。
使用instanceof,getClass检查Pair<Integer>,Pair<Double>,都是当做Pair类处理。
6.3不能抛出也不能捕获泛型类实例
泛型类不能继承异常类
public class Problem<T> extends Exception{ }//ERROR
不能捕捉类型变量,以下代码无法编译
public <T extends Throwable>void do(Class<T>t) { try{ ..... } catch(T e){............}}
可以声明泛型异常
public static<T extends Throwable>void do(T t)throws T { throw t;}
6.4参数化类型数组不合法
Pair<String>[]table=new Pair<String>[10];//ERROR
假设以上代码可以通过
Object[] obj=table;obj[2]=new Pair<Double>;//可以通过类型检查,和数组原类型冲突,所以禁止创建参数化类型数组。
Pair<String>[]table=new Pair[10];//注意,这样可以通过,Object[] obj=table; obj[2]=new Pair<Double>; //可以存,可以取
可以使用集合ArrayList收集参数化类型对象:ArrayList<Pair<String>> s=new ArrayList<Pair<String>>();
6.5不能实例化类型变量
不能实例化像new T(),new T[],T.class
如:public Pair(){first new T();second =new T();}//ERROR
原因是泛型的类型擦除,任何泛型类默认类型为Ojbect,如果new了Object对象那就不能进行强转了。
但是我们可以利用反射建立泛型对象。不过不是T.class.newInstance();//ERROR。
例子:
public static <T>Pair<T> makePair(Class<T> cl){ try{return new Pair<T>(cl.newInstance(),cl.newInstance);} catch(Exception e){return null;}}Pair<String>p=Pair.makePair(String.class);
注意:class<String> cl=String.class;每个类型都有1个且唯一的class实例。
ArrayList的数组是怎么实现呢?看下面:
public class ArrayList<E>{ private Object []elements; @SuppressWarnings("unchecked") public E get (int n ){ return (E)elements[n];} public void set(int n,E e){elements[n]=e;}}
实际上是使用了泛型数组
public class ArrayList<E>{ ArrayList(){elements=(E[])new Object[10];} E[]elements;}
返回一个泛型数组的方法
public static <T extends Comparable>T[] minmax(T[]a){ T[] mm=(T[])Array.newInstance(a.getComponentType(),2); ...................}
6.6泛型类的静态上下文中类型变量无效
泛型类中不能定义带类型变量的静态变量和静态方法
注意:泛型方法可以是静态的。
6.7 注意擦除后的冲突
public class Pair<T>{ public boolean equals(T value){retrun first.equals(value)&&second.equals(value);}}
这是一个重写的equals方法,这时将出现两个方法签名一样的equals方法,所以就产生冲突:
首先、我们都知道子类方法要覆盖,必须与父类方法具有相同的方法签名(方法名+参数列表)。而且必须保证子类的访问权限>=父类的访问权限。 然后、在上面的代码中,当编译器看到Pair<T>中的equals(T)方法时,第一反应当然是equals(T)没有覆盖住父类Object中的equals(Object)了。 接着、编译器将泛型代码中的T用Object替代(擦除)。突然发现擦除以后equals(T)变成了equals(Object),糟糕了,这个方法与Object类中的equals一样了。基于开始确定没有覆盖这样一个想法。然后得出两个结论:没有覆盖。但现在一样造成了方法冲突了。
解决方法只能是改变方法名。。。
泛型规范有一个原则:要支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。通常出现在继承一个类时,并不知道这个类实现了子类早也实现了的接口。
class A imlpements Comparable<String>{} class C imlpements A,Comparable<Double>{}//ERROR</span>
以上代码如果没带有类型限定,那么是合法的。
错误原因:如果实现了Comparable接口的compareTo方法,会有Comparable的桥方法,而类型限定有两个类型。桥方法不能有两个。
7.0泛型的继承规则
Pair<Employee>和Pair<Manager>不是子父类关系,他们不能互相转换类型。
Pair<Employee> a=new Pair<Employee>();可以转换类型为Pair b=a;这里要注意:b可以调用setFirst,参数可以为任何类型,但是a提取时,可能不是Employee类型。。。
泛型继承规则,父类是泛型类,子类继承时父类必须确定类型变量,要么就不写泛型限定:class A extends Pair<String> or class A extends Pair
当继承一个泛型类时如class A <T>extends Pair,Pair的泛型特性将失效,只会作为一个普通类,T被擦除为Object。
ArrayList<Manager>类型可以转换为List<Manager>. ArrayList<Employee>类型可以转换为List<Employee>类型
8.0通配符
通配符是泛型技术的延生,不能单独存在,要依赖于泛型。通配符?指任何类型,但是却不能代表任何类型,类似于null(指不指向任何对象),可以理解为说明符号。
? extend Employee:指继承Employee类的任何类。? super Manager :指Manager的任何父类
现在来说说通配符的几种使用情况:
1.class A <T>{ } 定义类时不能使用通配符。
2.public static void printBuddies(Pair<? extends Employee> p){ }作为方法参数类型的类型限定,参数类型Pair<T>的类型限定T只能是 Employee和它的子类。
public static void printBuddies(Pair<? super Manager> p){ }作为方法参数类型的类型限定,参数类型Pair<T>的类型限定T只能是 Manager和它的父类。
3.Pair<? super Manager> =new Pair<Manager>();作为定义对象时对象类型的类型限定,但是不能在new Pair<String>()中使用通配符。
这里要分析下2.3点,2.3点中p对象已经被转换为Pair<? super Manager> ,带通配符的类型和一般的有什么不同呢?
不同通过方法的调用体现:
Pair<? extends Employee> p=new Pair<Manager>();调用p.setFirst(new Manager());//ERROR 。set方法参数类型为? extends Employee,表面上看传入new Manager()没错,但是想想? extends Employee first=? extends Employee 传递值,如果可以传递Manager对象,也能传递一个非Manager型子类对象,在p对象中就能存任何子类对象,这个和泛型类型限定只有一个类型相矛盾,而且混杂的子类类型,类型安全不存在了。调用p.getFirst();可行,因为无论什么类型,只要赋值给Employee这个父类的指引就行了。(总结还是不足,以后有精力再想)
Pair<? super Manager> p=new Pair<Manager>();调用p.setFirst(new Manager());//通过,这里参数类型要求Manager的父类,因为多态,只要是(? super Manager)的子类Manager或者Manager的任何子类都能赋值给? super Manager类型指引。调用p.getFirst();可行,?没有上限,只能赋值给Object类型指引。
Pair<? extends Employee> p=new Pair<Manager>(new Manager(),new Manager());这时first的类型为? extends Employee,为什么构造器能传值呢? 只有new Pair<Manager>的<Manager>对构造器的参数类型有限制作用,在new对象时构造器是独立的,不受Pair<? extends Employee>类型影响,Pair<? extends Employee>只能在调用方法,域时决定T。
4.public static <T extends Comparable<? super T>> T min(T[]a){ }定义泛型方法时使用。T为GregorianCalendar设计
8.2无限定通配符
Pair<?>?代表任何类型
方法:? getFirst(), void setFirst(? a)
无法调用set方法,而get方法返回值只能赋给Object类型指引
作用:当不涉及类型时方法不受影响。
public static boolean hasNulls(Pair<?> p){ return p.getFirst()==null || p.getSecond()==null;}//等价于以下方法,但是通配符版可读性强public static <T>boolean hasNulls(Pair<T> p){ return p.getFirst()==null || p.getSecond()==null;}
8.3通配符捕获
这是个交换变量的方法,我们不能直接定义带有通配符类型的方法,域,so不能编译
<span style="font-family: Arial, Helvetica, sans-serif;">public static void swap(Pair<?> p)</span>
{ ? t=p.getFirst();//ERROR p.setFirst(p.getSecond()); p.setSecond(t);}
这是个泛型版本,可以编译运行
public static <T>void swapHelper(Pair<T> p){ T t=p.getFirst();//ERROR p.setFirst(p.getSecond()); p.setSecond(t);}
一定要使用通配符来定义,该怎么做
public static void swap(Pair<?> p){ swapHelper(p);}
swapHelper方法参数T捕获了通配符
通配符有很多限制,编译器必须能够确信通配符表达的是单个的,确定的类型。例如Array<Pair<T>>永远不能捕获Array<Pair<?>>,数组列表可以保存两个Pair<?>分别针对?的不同类型
12.9泛型和反射
Class类是泛型类,任何类型的Class对象都只有一个,如:Class<String>类唯一对象String.class
9.1使用Class<T>参数进行类型匹配
public static <T> Pair<T> malePair(Class<T> c)throws InstantiationException,IllegalAccessException
{
return new Pair<T>(c,newInstance(),c.newInstance());
}
makePair(Employee.class);
返回的类型为Pair<Employee>
9.2虚拟机中的泛型类型信息
泛型程序设计