在jdk1.5之前,集合中可以装多种类型不同的元素,由于一些原因,在jdk1.5推出了泛型,用于在编译阶段限制放入集合中的元素为同一类型。对于没有使用泛型的集合又称为原始类型泛型。
// 原始类型集合
ArrayList arr = new ArrayList();
arr.add("abc");
arr.add(123);
arr.add(false);
// 使用泛型的集合
ArrayList<String> arr0 = new ArrayList<>();
arr0.add("abc");
arr0.add(123); // 错误,只能添加String类型的元素
为什么说泛型是只对编译器器作用的呢?因为泛型会在编译的使用其约束作用,但是在生成字节码文件的时候会将泛型去掉。所以可以在程序运行的时候通过反射向集合中添加其他类型的元素。
// 通过反射向集合中写入不同于参数类型的值
ArrayList<Integer> str = new ArrayList<Integer>();
// str.add("bb"); // 直接加可定是不行的,会有泛型的限制
str.getClass().getMethod("add", Object.class).invoke(str, "abc");
System.out.println( str.get(0));
// 因为编译器在编译之后就把泛型去掉了,所以两个对应的是同一个class
Collection<String> col1 = new ArrayList<>();
System.out.println(col1.getClass() == str.getClass());
另外,不能因为jdk1.5有了泛型,就导致jdk1.5之前的程序不能使用,所以1.5的泛型是可以兼容之前的程序的。
ArrayList arr1 = new ArrayList(); // 正确
ArrayList arr2 = new ArrayList<String>(); // 正确
arr2.add(23);
arr2.add(false);
// arr2之所以可以添加其他类型的值,因为arr2的本质是一个原始类型的集合,虽然将String泛型的集合赋值给了arr2,但是arr2还是原始类型的集合。
ArrayList<String> arr3 = new ArrayList(); // 正确
ArrayList<String> arr31 = arr2; // 正确
// 原始类型集合可以存放多种类型的值,但是也是可以将他赋值arr3,因为为了兼容1.5之前的程序嘛
ArrayList<Integer> arr4 = arr2; // 正确
// 将arr2赋值给arr4,,没有问题。为了兼容1.5之前写的代码嘛。但是你在这之后添加的元素类型就得是Integer类型的
// arr4.add("23");
System.out.println(arr4.get(0));
System.out.println(arr4.get(1));
// 在泛型中,是没有继承的关系的
// ArrayList<String> arr4 = new ArrayList<Object>(); // 错误的
// ArrayList<Object> arr4 = new ArrayList<String>(); // 错误的
在使用泛型的时候,可以使用?表示泛型,也可以对?表示的泛型做一些限制。
ArrayList arr = new ArrayList();
arr.add("xxx");
arr.add(56);
printCollection(arr);
// Collection<?> 表示在调用方法的时候传递任意类型的泛型
public static void printCollection(Collection<?> collection){
// 在使用泛型通配符时,不能使用与泛型参数类型有关的方法。毕竟在运行时不能确定泛型的参数类型。
// collection.add("aa");
for(Object obj : collection){
System.out.println(obj);
}
}
使用extends或super对泛型类型做一些限制。
// exrends 这个表示?接受的泛型类型必须是继承自Number类的,有称为上边界
// ArrayList<? extends Number> a1 = new ArrayList<String>(); // 错误
ArrayList<? extends Number> a1 = new ArrayList<Integer>(); // 正确
// super 表示?接受的泛型类型必须是Number类的父类,又称为上边界
ArrayList<? super Number> a2 = new ArrayList<Object>(); // 正确
// ArrayList<? super Number> a2 = new ArrayList<Integer>(); // 错误
另外,你也可以在方法中自定义泛型。定义的泛型要写在返回值前面,并且要使用<>括起来你的泛型。这个写法是java模仿C++的写法的。但是要注意,自定义的泛型的传入值不能是基本类型的引用。
swap(new String[]{"abc","123","sss"}, 0, 1);
// swap(new int[]{2,3,4}, 1, 0);
// 报错,使用自定义泛型时,自定义的泛型的传入值不能是基本类型的引用
swap(new Integer[]{2,3,4}, 1, 0); // 正确
// 自定义泛型, 可以对泛型做一些限制
// <T extends Object> 写在 返回值前面的,表示声明了一个类型。模仿C++的写法。
public static <T extends Object> void swap(T[] arr, int x, int y){
T tmp = arr[x];
arr[x] = arr[y];
arr[y] = tmp;
}
那么自定义泛型的用途有哪些呢?先来看一个简单的例子。
/*
* 假如有这样的需求:在添加的时候可以添加任意类型的数值,查找时返回其添加的类型
* 那么我就可能会在add方法上使用自定义泛型,这样就可以添加任意的类型了。
* 在find方法上也使用自定义泛型。不就行了嘛。可是真的行吗?
*/
public class GeneralDao {
public <T> void add(T obj){
// doSomething
}
public <T> T findById(int id){
// doSomething
return null;
}
}
使用时:
GeneralDao dao = new GeneralDao();
dao.add("Str"); //添加String类型
int a = dao.findById(0);
// 查找返回值类型是泛型,那岂不是可以赋值给任何类型嘛?
// 可是你添加什么类型,查找返回值就赋值给你添加的类型不就行了吗。
// 那么如果,添加和查找不是同一个人写的呢?
对于这种,涉及到类中有多个方法使用到泛型的问题,那么泛型就该使用类级别的泛型。这样才能起到添加什么类型就返回什么类型的要求。
使用类级别的泛型:
// 类级别的泛型使用,和jdk帮助文档中形式一样。
// 这样在创建对象的时候,就得指定T的类型,就会有,添加什么,返回什么的效果。
// 使用了类级别的泛型就不用再写方法级别的泛型了
public class GeneralDao<T> {
public /*<T>*/ void add(T obj){
}
public /*<T>*/ T findById(int id){
return null;
}
}
使用类级别的泛型:
GeneralDao<String> dao = new GeneralDao<String>();
dao.add("Str");
String s = dao.findById(0);
// 在查找的时,返回的类型就只能是你添加的类型。编译器会给出你添加的类型
// 这个就有点像java api给我们提供使用的集和泛型编译限制了
最后一个,如果你想要知道泛型的实际类型可以使用反射得到。
// 可以通过使用方法来获得泛型的实际参数类型。
Method m = Generictiy.class.getMethod("applyGeneral", Vector.class);
System.out.println(m.getGenericParameterTypes()[0]);
public void applyGeneral(Vector<Date> v){
}