1.体验泛型
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时去除掉“类型”信息,使程序运行小效率不受影响,对于参数化的泛型类型,getClass()方法返回值和原始类型完全一样.由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,在调用其add方法即可.
ArrayList<String> collection1 = new ArrayList<String>();
ArrayList<Integer> collection2 = new ArrayList<Integer>();
System.out.println(collection1.getClass() == collection2.getClass()); //true
public static void main(String[] args) throws Exception { ArrayList<String> collection1 = new ArrayList<>(); collection1.add("abc"); ArrayList<Integer> collection2 = new ArrayList<>(); System.out.println(collection1.getClass() == collection2.getClass()); //true collection2.getClass().getMethod("add", Object.class).invoke(collection2, "abc"); System.out.println(collection2.get(0)); //abc }
参数化类型不考虑类型参数的继承关系:
Vector<String> v1 = new Vector<Object>();//错误!不写<Object>没错
Vector<Object> v2 = new Vetcor<String>(); //错误
在创建数组时,数组的元素不能使用参数化的类型,例如,下面语句有误
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面代码会报错么?
Vector v1 = new Vector<String>(); //参数化类型给原始类,不报错
Vector<Object> v2 = v1 ; //原始类型给参数化类型,可以
2.泛型的通配符扩展应用
泛型中的 ? 通配符
?表示任意类型
定义一个方法,该方法用于打印出任意参数化类型集合中的所有数据,该方法如何定义?
public void printCollection(Collection<?> cols){ for(Object obj: cols){ System.out.println(obj); } //cols.add("string") //错误,因为它不知道自己未来匹配就一定是String add()方法跟类型参数有关系 cols.size(); //没错,此方法与类型参数没有关系 cols=new HashSet<Date>(); //? 可以引用其他类型 }
总结:
使用 ? 通配符可以引用其他各种参数的类型, ? 通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
限定通配符的上边界:
正确: Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>(); //要求参数类型需是Integer的父类就为Number或者Number的父类。
限定通配符总是包括自己。
泛型集合的综合应用案例
对在jsp页面中也经常要对Set或Map集合进行迭代:
<c:forEach items="${map}" var="entry">
${entry.key}:${entry.value}
</c:forEach>
3.定义泛型方法
交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] int i,int j){
E t = E[i];
E[i] = E[j];
E[j] = t;
}
>用于防止泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回值类型之前,
也就是紧邻返回值之前。按照惯例,类型参数通常用打那个大写字母表示。
>只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。
> 然而对于add方法,使用基本类型的数据进行测试没有问题,这是因为自动装箱和自动拆箱了。
4.泛型方法的练习
>编写一个方法,自动将Object 类型的对象转换成其他类型
public static void main(String[] args) throws Exception { Object obj = "abc"; String x3 = autoConvert(obj); } private static <T> T autoConvert(Object obj){//在返回值的前边,用一个<>来说明类型,或者传递一个新的类型
return (T)obj; }
5.自定义泛型类
package com.java.day2; import java.util.Set; /** * 自定义泛型类 * @author Administrator * * @param <E> */ public class GenericDao<E> { //类中说明类对象中使用的是同一中参数 public void add(E x){ } public E findById(int id){ return null; } public void delete(E obj){ } public void delete(int id){ } public void update(E obj){ }
public static <E> void update2(E obj){
}
public Set<E> findByConditions(String where){ return null; } }
GenericDao<ReflectPoint> dao = new GenericDao<>();
注意:静态方法中不能使用与类相同的范型类型(静态方法执行时,类还没有实例化)
public static <E> void update2(E obj){
}
这样写就认为是独立的,跟类没有关系了。
编译器的去泛型类型化
编译器编译后认为两个是相同的(这不是重载)
6. 通过反射获得泛型的实际类型参数
public static void main(String[] args) throws NoSuchMethodException, SecurityException { Object obj = "abc"; String x3 = autoConvert(obj); System.out.println(x3); Vector<Date> v1 = new Vector<Date>(); //通过变量名成 v1是无法得到 实际的泛型参数类型,但是把这个变量传给一个方法使用时 //可以通过反射,得到方法的实际参数类型的 //通过反射的方式,得到泛型里的实际类型 Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class); Type[] types = applyMethod.getGenericParameterTypes(); //获取参数的泛型类型,返回数组 ParameterizedType pType = (ParameterizedType) types[0]; //方法中的第一个参数 System.out.println(pType.getRawType()); //获取原始的类型 class java.util.Vector //很多数据库框架用了此方法,把数据库查询出的记录,转为实际的参数类型,必须先得到实际的参数化类型 // System.out.println(pType.getActualTypeArguments()[0]); //得到实际参数化类型,可能有多个Map<K,V> class java.util.Date } public static void applyVector(Vector<Date> v1){ }
很多框架就是根据这个方法,把数据库查出的对象转换成泛型的实际化参数(前提需要获取得知泛型的实际化参数是什么类型的)
Hibernate 就是利用此方法,获取泛型的实际化参数类型