泛型的概述:
没有使用泛型时,只要是对象,不管什么类型,都可以存储进同一个集合中。使用泛型集合,可以将集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全,并且从集合中获取一个对象时,编译器也可以知道这个对象的类型,不需要进行强制类型转换。
泛型时提供给javac编译器使用的,可以限定集合中的类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去掉“类型“信息,使程序运行效率不受影响。由于编译生成的字节码会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其他类型的数据。例如:用反射得到集合,在调用add方法。
一、术语:
1、整个称为ArrayList<E>泛型类型
2、ArrayList<E> 中的E称为类型变量或类型参数
3、整个ArrayList<Integer> 称为参数化的类型
4、ArrayList<Integer>中Integer称为类型参数的实例或实际类型参数
5、ArrayList<Integer>中的<>念typeof
6、ArrayList称为原始类型
二、参数化类型与原始类型的兼容性
Vector<String > v = new Vector(); //警告,
Vector v = new Vector<Object>(); //警告
三、参数化类型不考虑类型参数继承关系:
Vector<String > v = new Vector<Object>(); //错误,不写object没错
Vector<String > v = new Vector<Object>(); //错误,不写object没错
四、创建数组实例时,数组元素不能使用参数化的类型(编译器不允许创建类型变量的数组)
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:
Vector v1= new Vector<String>();
Vector<Object> v =v1;
//两句都没错,编译器严格按照语法检测的工具 一句一句翻译
五、?通配符
使用通配符(?)可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数无关的方法,不能调用与参数有关的方法。
public static void printCollection(Collection<?> collection){
//collection.add("abc"); 不能调用与类型有关的方法
System.out.println(collection.size());
for(Object obj:collection){
System.out.println(obj);
}
collection = new HashSet<Date>();//正确
}
六、通配符的扩展
1、限定通配符的上边界
Vector<? extends Number> x = new Vector<Integer>();//正确
Vector<? extends Number> x = new Vector<String>();//错误
2、限定通配符的下边界
Vector<? supper Integer > x = new Vector< Number >();//正确
注意:限定通配符总是包括自己
Class<?> y;
Class<String> x;
y=x; //正确
x=y;//错误
- 泛型集合的综合案例
HashMap<String,Integer> map= new HashMap<String, Integer>();
map.put("zhangsan", 23);
map.put("f", 32);
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
for (Map.Entry<String, Integer> entry:entrySet){
System.out.println(entry.getKey()+":"+entry.getValue());
}
- 自定义泛型方法
Java中的泛型类型类似于c++中的模板,但这种相识仅限于表面。Java语言中的泛型基本上完全是在编译器中用于编译器执行检查和类型推段,然后生成非泛型的字节码,这种实现技术称为擦除。扩展虚拟机指令集支持泛型认为是无法接受的。
1、 用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法返回类型之前,也就是紧邻返回值之前,类型参数通常用单个大写字母表示。
2、 只有引用类型才能作为泛型方法的实际参数,对与add方法,使用基本类型的数据进行测试没有问题,因为会自动装箱和拆箱。Swap(int[],1,2)会报错,是因为编译器不会对int[3]中的int自动拆箱和装箱,因为int[3]本身已经是对象了。
private static <T> T add(T x,T y) {
return null;
}
private static <T> void swap(T[] a,int i,int j){
T tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
3、 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,并且可以用&来指定多个边界。
例如:<V extends Serializable&cloneble> void method(){}
4、 普通方法、构造方法和静态方法中都可以使用泛型。
5、 编译器不允许创建类型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。例如:Vector<Integer> vectorList[]=new Vector<Integer>[10](我)
6、 异常采用泛型
Public static <T extends Exception > sayHello() throws T{
Try {}catch( Exception e){ throw (T)e;}}
7、 在泛型中可以同时有多个类型参数,在定义他们的尖括号用逗号分开。
例如:public static <K,V> V getValue(K key){return map.get(key)}
8、/泛型方法类型参数的类型推断(参数列表,返回值)
1·只有一处使用类型参数,就是该类型。
2·多处使用统一类型,就是该类型。
3·多处使用类型不是同种类型,则取交集。
4·多处使用类型不是同种类型,且有返回值类型,则优先考虑返回值类型。
参数类型的类型具有传递性,第二种会报错。
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)/
- 自定义泛型类型
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要操持同一个实例类型时,就要采用泛型类型的方式进行定义。
语法格式:public class Genoric<T>{
Private T field1;
Public void save( T obj){}
Public T getById(int id){return T}
}
注意:
1、在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2、当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),
而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。(静态方法可以使用方法级别的泛型)静态成员不用创建对象就可以调用。(对象的类型)
- 通过反射获得泛型的参数化类型
通过变量自身是没法知道它的泛型,当把变量交给方法作为参数或者返回值去使用时,方法类提供了一些方法来获得参数列表,并且以泛型类型获得参数列表。(就是获得方法接受参数的实际类型)
方法类上有相应的方法:getGenericReturnType()获得泛型的返回类型
\getGenericParameterTypes 获得泛型的参数类型
GetParameterTypes() 获得参数列表
public static void applyVector(Vector<Date> v1){
}
/*public static void applyVector(Vector<Sting> v1){
}不是重载 去类型化*/
Method applyMethod =GenericTest.class.getMethod("applyVector", Vector.class);
Type[] types=applyMethod.getGenericParameterTypes();
//参数化的类型
ParameterizedType pType = (ParameterizedType)types[0];
System.out.println(pType.getRawType());//原始的类型
//得到实际类型参数
System.out.println(pType.getActualTypeArguments()[0]);