----------------------ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
泛型在众多特性中,是最难掌握的一部分
泛型的出现
在jdk1.5前是用Object
example:
public class GenericTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList collection=new ArrayList();
collection.add(1);
collection.add(2L);
collection.add("abc");
for (Object obj : collection) {
int i=(Integer)obj;
System.out.println(++i);
}
}
}
这样就会出现运行时异常,程序就会停下,这不是我们想看到的
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
at cn.itcast.day2.GenericTest.main(GenericTest.java:17)
用Object只是主现的去避免异常的发生,无法在程序编译时及时发现异常,
这个时候用泛型就可以将这个异常转移到了编译时期,不用程序员再主观的认定这里会不会有异常发生
ArrayList<String> collection1=new ArrayList<String>();
collection1.add("a");
collection1.add("abc");
for(Iterator<String> it=collection1.iterator();it.hasNext();){
String element=it.next();
System.out.println(element);
}
我们看到用泛型,类型转换也不用再做了
泛型是在定义集合的时候声明要装什么类型的元素,如果装的元素不是这个类型,编译就会出错
除了在集合中可以用泛型,还有好多地方可以用,比如 反射
example:
Constructor<String> constructor=String.class.getConstructor(StringBuffer.class);
String s=constructor.newInstance(new StringBuffer("abc"));
System.out.println(s.charAt(2));
总结
在使用泛型之前,不管什么类型的对象,都可以存储进同一个集合中。
在会用泛型之后,可以将一个集合限定为一个特定类型,集合中只能存储这一种类型的对象,这样就更安全了;
并且在取出集合的元素时,编译器就知道这个对象的类型,不需要对对象进行强制类型的转换了
在jdk1.5中,还可以用原来的方式将各种不同类型的数据存放到同一个集合中,但编译器会报告unchecked警告
泛型的内部原理及更深的应用
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序的非法输入,
编译器编译带类型说明的集合时,会去掉类型信息,使程序运行效率不受影响
对于参数化的泛型类型,getClass()方法的返回值类型与原始类型完全不一样,由于编译生成的字节码会去掉泛型的类型信息
只能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如:用反射得到集合,再调用其add方法即可
ArrayList<Integer> collection2=new ArrayList<Integer>();
System.out
.println(collection2.getClass() == collection1.getClass());
collection2.getClass().getMethod("add", Object.class).invoke(collection2, "aaa");
System.out.println(collection2.get(0));
运行结果
aaa
这样就说明泛型的出现只是给编译器看的
这里在以后运用注意的
整个ArrayList<E>称为泛型类型
ArrayList<E>中的E称为类型变量或类型参数 它是一个变量,它指向的不是普通值 而是指向了类型
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性
Collection<String> c=new Vector();
Collection c1=new Vector<String>();
这两种方式都可以通过编译
参数化类型不考虑参数的继承关系
Vector<String> v=new Vector<String>();如果=两边都有<>那么<>中的内容必须一样,左边是?通配符除外
在创建数组实例时,数组的元素不能使用参数化的类型
如 Vector<String> vectorList[]=new Vecort<String>[10];这句话是错误的
思考:下面的代码会报错吗
Vector v=new Vector<String>();
Vector<Object> v1=v;
不会报错
编译器在编译时,是一行一行的扫描代码,第一行参数化类型赋给原始类型可以,第二行原始类型赋给参数化类型也可以
通配符
使用?通配符可以引用其他各种参数化类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法
限定
? extends number//向上限定
? super Integer//向下限定
Map<String, Integer> maps = new HashMap<String, Integer>();
maps.put("zxx", 27);
maps.put("flx", 33);
maps.put("lhm", 38);
/*Set<String> keys=maps.keySet();
for(Iterator<String> it=keys.iterator();it.hasNext();){
String key=it.next();
System.out.println(key+":"+maps.get(key));
}*/
Set<Map.Entry<String, Integer>> entrySet=maps.entrySet();
/*for(Iterator<Map.Entry<String, Integer>> iterator=entrySet.iterator();iterator.hasNext();){
Map.Entry<String, Integer> entry=iterator.next();
System.out.println(entry.getKey() + ":" + entry.getValue());
}*/
for(Map.Entry<String, Integer> entry:entrySet){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
自定义泛型方法
add(3,5);
Number x=add(3.5,5);返回值为两个参数的父类类型 类型推断
Object obj=add(3,"abc");
}
public static <T> T add(T x, T y){
return null;
}
泛型方法的定义
在定义时定义在返回值之前,用<>表示
总结
用于放置泛型的类型参数的尖括号,应出现在方法的其他所有修饰符之后和方法的返回值之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示
只有引用类型才能作为泛型方法的实际参数
public static <T> void swap(T[] arr,int index1,int index2){
T temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
swap(new int[]{1,2,3,4},1,3);这条语句是错误的
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,如:Class.getAnnotation()方法的定义,并且可以用&来定义多 个边界,
如:<V extends Serializable&Cloneable> void method(){}
普通方法,静态方法和构造方法中都可以使用泛型;编译器也不允许创建类型变量的数组
也可以用类型变量表示异常,称为参数化的异常,可以用于throws列表中,但不能用于catch子句中
private static <T extends Exception> sayHello()throws T{
try{
}catch(Exception e){
throw (T)e;
}
}
在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分开,如:
public static <K,V> V getValue(K key){return map.get(key);}
类型参数 的类型推断
编译器判断泛型方法实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程
根据调用泛型方法时传递的实际参数类型或返回值的类型来推断,具体规则如下
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来确定泛型参数的类型。如:
swap(new String[3],1,2)->static <E> void swap(E[],int i,int j)
当某个类型变量只在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应了同一种类型,这也很容易凭着感觉推断出来,如:
add(3,5)->static <T> T add(T a,T b)
当某个类型变量只在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应到了不同类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如:下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f)-> static <T> void fill(T[] a,T v)
当某个类型变量只在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应到了不同类型,并且使用到了返回值,这时候优先考虑返回值类型,例如:下面语句中实际对应的类型就是Integer了,编译将报告错误,将变量的类型改为float,对比eclipse报告的错误提示,将变量x类型改为Number,则没有错误了:
int x=add(3,3.5f)->static <T> T add(T a, T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,第二种情况则根据参数化的Vector实例将类型变量直接确定为String类型,编译将出问题:
copy(new Integer[5],new String[5])->static <T> void copy(T[] a,T[] b)
copy(new Vector<String>(),new Integer[5])->static <T> void copy(Collection<T> a,T[] b)
根据张老师讲的内容用自己思想总结 一下
类型推断就是
当传递的参数中有指定了泛型的对象时(比如集合用的时候要指定泛型),那么这个方法的泛型的实际参数类型就是这个对象的泛型类型
如果传递的参数中没有用到指定了泛型的对象,那么就取传递的实际参数的共同的父类(最接近这几个参数的父类)
泛型类
当我们需要 一个类中操作的对象是同一个时,但这个对象我们又不确定,这时,我们可以不在方法上定义泛型
而是在类上定义 在方法上用
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型
当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数
----------------------ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------