先不管范型是什么,首先建立一个感性的认识
GenericTest.java
import java.lang.reflect.*;
import java.util.*;
public class GenericTest {
public static void main(String args[])throws Exception{
/*集合用到范型 */
ArrayList <String> collection=new ArrayList<String>();//只能存储String类型的值
collection.add("abc");
collection.add("lijin");
String element=collection.get(1);
System.out.println(element);
//因此也只能取出String类型
/*反射用到范型的好处*/
Constructor<String>constructor1=String.class.getConstructor(StringBuffer.class);
String str2=constructor1.newInstance(new StringBuffer("abd"));
System.out.println(str2.charAt(2));
/*总结:
* 没用到范型时只要是对象,不管什么类型的对象都可以存储进同一个集合中,
* 使用范型集合,可以将一个集合中的元素限定为一个特定的类型,集合中只能
* 存储同一个类型的对象,这样更安全,并且当从集合中获取一个对象时,编译
* 器也可以知道这个对象的类型,不需要对对想进行强制类型转换*/
/*范型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序的
* 非法输入,编译器编译带类型的说明集合时会去掉“类型”信息,使程序运行效率不
* 受影响,对于参数化的范型类型,getClass()方法的返回值和原始类型完全一样,由于
* 编译生成的字节码会去掉范型的类型信息,只要能掉过编译器就可以往某个范型集合
* 中加入其他类型的数据,例如,用反射得到集合,再调用其add()即可。
*
* */
ArrayList<Integer>collection3=new ArrayList<Integer>();
System.out.println(collection3.getClass()==collection.getClass());//编译完后字节码一样
/*用反射的方法跳过编译器*/
collection3.getClass().getMethod("add", Object.class).invoke(collection3,"abc");
System.out.println(collection3.get(0));
}
}
了解范型
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>范型类型
ArrayList<E>中的E称为类型变量或者类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<typeof>
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如:
Collection<String>c=new Vector();//可以编译
原始类型可以引用一个参数化类型的对象
Collection c=new Vector<String>();//可以编译,原来的方法接受一个集合参数
参数化类型不考虑类型参数的继承
Vector<String>v=new Vector<Object>();//编译错误
Vector<String>v=new Vector<String>();//错误
在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer>vectorList[]=new Vector<Integer>{10};
思考:下面代码会报错吗?
Vector v1=new Vector<String>();
Vector <Object>v=v1;
以上代码可以编译通过
范型中的?通配符
public static void printCollection(Collection<?> collection){
//collection.add("String");//错误,因为它不知自己未来匹配就一定是String
System.out.println(collection.size());//没错,此方法与类型参数没有关系
for(Object obj:collection){
System.out.println(obj);
}
}
总结
范型中的?通配符
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以
调用与参数化无关的方法,不能调用与参数化有关的方法
通配符的扩展
限定通配符的上边界:
正确: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>();
提示;限定通配符总是包括自己。
范型集合的综合案列
HashMap<String,Integer>maps=new HashMap<String,Integer>();
maps.put("zxx",25);
maps.put("lijn", 19);
maps.put("dsf",45);
//对maps进行迭代
Set<Map.Entry<String,Integer>>entrySet=maps.entrySet();
for(Map.Entry<String,Integer>entry:entrySet){
System.out.println( entry.getKey()+":"+entry.getValue());
}
自定义范型方法及其应用
交换数组两个元素的位置
swap(new String[]{"abc","xyz","itcast"},1,3);
private static <T> void swap(T []a,int i,int j){
T tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
1 用于放置范型的类型参数的尖括号应出现在方法的其他所有修饰符之后,和在方法的返回类型之前
也就是紧邻返回值之前,类型参数通常用单个大写字母。
2 只有引用类型才能作为范型方法的实际参数,swap(new int[3]3,5);语句会报编译错误
3 除了在应用范型时可以使用extends限定符,在定义范型时也可以使用extends限定符,例如:
Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,如<V extends Serializable&cloneable>void method(){}
4 普通方法,构造方法和静态方法中都可以使用范型
5 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表但是不能用于catch子句中。
private static <T extends Exception>sayHello()throws{
try{
}catch(Exception e){
throw(T)e;
}
6 在范型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分,例如:
public static <K,V>V getValue(K key){
return map.get(key);
}
范型方法的练习题:
1.编写一个范型方法,自动将Object类型的对象转换成其他类型