泛型:JDK1.5以前的集合类
看下如下代码:
ArrayList collection1 = new ArrayList();
collection1.add(1);
collection1.add(1L);
collection1.add("abc");
int i = (integer)arrayList.get(1);//这里编译需要强制类型转换且运行时出错
这个就是可以添加各种对象,是不确定的
JDK1.5的集合类希望你在定义集合时,明确表示你要向集合中装哪种类型的数据,无法加入指定类型以外的数据
如下面这段代码:
ArrayList<String> collection2 =newArrayList<String>();
//collection2.add(1); //编译时报语法错误
//collection2.add(1L); //编译时报语法错误
collection2.add("abc");
String element = collection2.get(0);
没有泛型之前,代码:
Constructor constructor =String.class.getConstructor(StringBuffer.class);
String str=(String)constructor .newInstance(new StringBuffer("abc"));
System.out.println(str.charAt(2));
用了泛型,代码:
Constructor<String> constructor1 = String.class.getConstructor(StringBuffer.class);
try {
String str2 = constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str2.charAt(2));
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。所以我们可以跳过这个编译器过程,我们直接用反射得到某个方法再去invoke去调用这个方法
代码:
ArrayList<Integer> collection3 = new ArrayList<Integer>();
System.out.println(collection3.getClass() == coolection2.getClass());
// collection3 .add("abc");
// 泛型是给编译器看的,纬编一起提供编译方法,我们运行的时候看不到泛型的痕迹
try {
collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");
System.out.println(collection3.get(0));
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
专业术语:
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型//Parametered 已经参数化过了的 ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof(类型为)
ArrayList称为原始类型
参数化类型与原是类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如:
原始类型可以引用一个参数化类型的对象,编译报告警告,例如:
Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //报错,不写<object>不会报错,写了类型不匹配
Vector<Object> v = new Vector<String>(); //同上报错
在创建数组实例时,数组的元素不能使用参数化的类型,例如:
Vector<Integer> vectorList[] = new Vector<Object>[10];
实例:
Vector v1 = new Vector<String> ();
Vector<Object> v =v1;
这两行编译不会报错,因为编译器是一行一行扫描的,我们去看待这句语句编译会不会出错,是去要想单单这一句会不会报错,而不是去想整个程序运行下来会不会报错。
泛型中的?通配符:
代码示例:
public static void printCollection (Collection<?> collection) {
/*
* 泛型通配符?表示可以匹配任何类型;
* 假如我们的方法中带的泛型类型的参数,如果泛型类型指定了,那我们在方法中所用的此泛型中的类型要完全一致,否则报错;
* 如果我们想用其他类型的,则就必须将带参时的泛型中的类型以泛型通配符?代替!
*/
// collection.add(1); 是错的,?不可以调用只带参数的方法
/*
* 但是如果用泛型通配符代替了,则方法调用的时候必须指明类型
* ?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
*/
System.out.println(collection.size()); //可以,因为size(),不带参
for(Object obj :collection) {
}
}
泛型中的?通配符的扩展:
限定通配符的上边界:
正确:Vector<? extends Number> x= new Vector<Integer>();
表示类型必须是Number类型或者是Number的子类
错误:Vector<? extends Number> x= new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x =new Vector<Number>();
表示类型必须是Integer类型或者是Integer的父类
错误:Vector<? super Integer> x =new Vector<Byte>();
Class<? > x; //返回的是泛型通配符?类型的
Class<String> s = null;//返回的是String类型的
s = x; //报错,不可以将不是泛型通配符?类型的给一个具体的不是?类型的String类型
x = s; //不报错,可以将String类型的给泛型通配符?类型
看一个应用,我们搞个方法,让这个方法交换任何类型数组里的任意两个元素的值:
private static <T> void swap (T[] a,int i,int j) {
T t = a[i];
a[i] = a[j];
a[j] = t;
}
swap(new String[] {"abc","acb","cab"},1,2);
// swap(new int[] {1,2,3},1,2); //这里只能用引用类型
用下面的代码说明对异常如何采用泛型:private static <T extends Exception> sayHello() throws T
{
try{
}catch(Exception e){//这里必须写成Exception而不能写成抓其他异常
throw (T)e;
}
}
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) {
return map.get(key);
}
普通方法、构造方法和静态方法中都可以使用泛型。
类型推断:
编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) ? static<E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) ? static <T> T add(T a, T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) ? static <T>voidfill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) ? static <T> T add(T a,Tb)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) ? static <T> voidcopy(T[]a,T[] b);
copy(new Vector<String>(), new Integer[5]) ? static<T>void copy(Collection<T> a , T[] b);
public static <T> void copy1 (Collection<T> dest,T[] src) {
}
public static <T> void copy2 (T[] dates,String[] strings) {
}
自定义泛型类的应用:
代码:
GenericDao dao = new GenericDao();
dao.add(new ReflectPoint(3, 4));
dao.fineById(2);
package cn.itcast.generic;
import java.util.Set;
/*
* dao data accss object——>crud(增删改查)
*/
public class GenericDao<T> {
public void add(T x) {
}
public T fineById(int id) {
return null;
}
public Set<T> findByConditions(String where) {
return null;
}
}
通过反射获得泛型的实际类型参数:
类Method上有个方法:Type[]getGenericParameterTypes() 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。
Type是个接口,子接口有ParameterTypes,里面有方法:
1.Type[]getActualTypeArguments() 返回表示此类型实际类型参数的 Type 对象的数组;
2.Type getRawType() 返回 Type 对象,表示声明此类型的类或接口;
3.Type getOwnerType() 返回 Type 对象,表示此类型是其成员之一的类型。
重点知识点:
public static void applyVector(Vector<Date> v1){
}
用反射获得这个方法中传递进来的类型,并得到实际类型,
class demo {
public static void main(String[] args) {
Method applyMethod = className.class.getMethod("applyVector",Vector.class);
Type[] types = applyMethod.getGenericParameterTypes(); //用这个反射获得了这个方法,返回这个类型
ParameterizedType pType = (ParameterizedType)types[0]; //强制转换一下类型
System.out.println(pType.getRawType()); //然后用这个类里的方法获取原始类型,输出Vector
System.out.println(pType.getActualTypeArguments()[0]); //然后再获得实际类型
}
}
------- android培训、java培训、期待与您交流! ----------