泛型是JDK1.5的新特性,类似于C++中的模板,但是仅限于表面。Java中的泛型只是在编译器中实现,用于编译时类型检查,编译后,生成普通的非泛型字节码,这种实现技术成为erasure“擦除”,Java之所以只在编译时实现,是因为若运行时支持泛型,需要拓展虚拟机指令集,工作量庞大,并且使Java厂商升级其JVM造成很大困难。
术语:
1. ArrayList<E> 泛型类型,其中E称为泛型变量或类型
2. ArrayList<Integer>参数化得泛型类型,其中Integer称为参数化的类型变量
3. ArrayList<Integer> <>念做:typeOf
4. ArrayList 原始类型
使用泛型之前,定义的集合可以加入任何类型的数据:
ArrayListlist = new ArrayList();
list.add("name");
list.add("123456");
list.add(100);
list.add(100L);
当从list中取出时,由于不知道存入的类型,所以每次取出都要进行类型转换:
String name = (String) list.get(0);
或者:
int age= (Integer) list.get(2);
即使list中所有对象均为String ,取出时仍然需要进行转换,否则会编译错误。
使用泛型后:
ArrayList<String> list = newArrayList<String>();
list.add("123");
list.add("abc");
String name = list.get(0);
list中只能加入string类型的数据,这样更安全,并且取出时不需要每次进行转换,直接定义为string即可。在使用泛型对集合的类型进行限制时,例如ArrayList<String>,这时,定义的集合只能加入string类型的数据,加入其它类型数据会编译错误。但是泛型只是在编译时有效,编译通过后,会去掉“类型”信息,也就是说ArrayList<String>和ArrayList<Integer>在编译之后(运行时),是完全一样的ArrayList对象,都没有了类型约束。
这样就可以通过两种方式“绕过”泛型的检测:
1、使用反射技术,可以“绕过”这种约束,向集合中加入其它类型的对象,例如:
ArrayList<Integer> list =new ArrayList<Integer>();
list.add(“abc”);
直接向list中加入字符串“abc”会编译错误。
如果通过反射调用list的add方法则可以向集合中加入字符串:
list.getClass().getMethod("add",Object.class).invoke(list, "abc");
System.out.println(list.get(0));
就可以成功的向集合中加入字符串,并且可以正常输出。
1、 利用兼容性
ArrayList<Integer> list = new ArrayList< Integer>();
这时,list只能加入Integer类型数据,但是,参数化类型和原始类型是有兼容性的,可以先定义一个基础类型集合:
ArrayList list0 =new ArrayList();
list0.add("username");
list0.add(100);
list0.add(100L);
在用list去引用定义的集合,即:
ArrayList<Integer> list = list0;
list.add(100); //引用后,可以继续向list中加入数据,但是只能加入Integer类型数据
参数化类型必须一致,引用自己的父类或者父类引用子类均是错误的:
ArrayList<String> list = newArrayList<Object>();
ArrayList<Object> list = newArrayList<String>();
自定义泛型:
注意:
1、只有对象类型如Integer,String,Double,Long,Object等才可以作为泛型的类型参数,基本数据类型不可以作为类型参数,如:int,long,double等不可以。
2、自定义泛型时,类型是自动转换的,自动取最大的或两个都能包括的:
public static <T> T add(T a ,T b){
return null;
}
上面这个自定义的泛型方法,若调用:
add(1,1); 则返回Integer
add(1.0,1.2); Double
add(1.0,1); Number
add(1,”abc”); Object
3、自定义泛型时,若参数已经是一个对象,则不会自动转换,并且只能使用对象类型的参数:
public static <T> Tadd(T[] a){ return null; }
这时,参数本身就是一个对象,若传入参数add(new int[]{});是错误的,int不能作为泛型的类型参数,只能传入add(newString[]{});或者add(new Integer[]{1,2});等对象类型参数。
4、自定义泛型时,还可以使用extends限定符,表示定义的类型限定继承的父类。
public static <T extends Number> Tadd(T[] a){
return null;
}
限定了extends Number之后,String等非Number子类就不可以作为类型参数,而Integer,Long,Double等Number子类则可以作为类型参数。
5、多个类型参数
如果方法需要多个参数类型,则可以定义方法时,使用多个参数类型,中间用“,”隔开,例如传入T、X、Y类型的参数:
public static <T,X,Y> voidadd(T t,X x,Y y){
return null;
}
6、返回类型的解释
若传入参数中存在类型T,返回T,则代表返回的类型和传入参数的类型一致,例如:
public static <T> voidadd(T t){
return null;
}
若返回的类型并没有在参数中存在,则代表需要什么类型,就返回什么类型,比如:
public static <T> void add(Object obj){
returnnull;
}
返回:Integer a = add(1);
Stringb = add("username");
需要返回Integer时,T就是Integer类型,需要返回String时,T就是String类型。