-------
声明:注解、泛型、反射和代理非常重要
1、泛型(Generic)的作用
JDK5以前,对象保存到集合中就会失去其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发程序的一些安全性问题。例如:
ArrayList list = new ArrayList();
list.add("abc");
Integer num = (Integer) list.get(0); //运行时会出错,但编码时发现不了
list.add(new Random());
list.add(new ArrayList());
for(int i=0;i<list.size();i++){
(?)list.get(i); //此处取出来的对象应转换成什么类型
}
JDK5中的泛形允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。
Java泛型类似于 C++ 中的模版,但这种相似仅体现在表面。
注意:泛型是提供给 Javac 编译器使用的,用于编译器执行类型检查和类型推断,让编译器在源代码级别上,即挡住向泛型类或方法或集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
2、泛型的内部原理以及深入应用
(1)泛形的基本术语,以ArrayList<E>为例:
a、整个 ArrayList<E> 称为泛型类型
b、ArrayList<E> 中的 E 称为类型参数或类型变量。
c、整个 ArrayList<Integer> 称为参数化的类型(ParameterizedType);
ArrayList<Integer> 中的 Integer 称为实际类型参数或类型参数的实例。
d、ArrayList 称为原是类型(primitive type),这是相对于泛型来说的一个称谓。与 JDK1.6中新特性 PrimitiveType 不是指的一个,通常我们说的原是类型是指的8个基本类型。<> 念着 typeof。
(2)参数化类型和原始类型的兼容性
a、参数化类型可以引用一个原始类型的对象:
Collection<String> coll = new HashSet();
b、原始类型可以引用一个参数化类型的对象:
Collection coll = new HashSet<String>();
(3)参数化类型不考虑类型参数的继承关系
LingkedList<String> link = new LingkedList<Object>();
LingkedList<Object> link = new LingkedList<String>();
虽然 = 两边参数类型是继承关系,但是这种格式不正确,所以可以得出如果 = 两边都有泛型,那么两个泛型必须一致,若只有一边有泛型,则不考虑在内。
特殊格式一:ArrayList<?> list = new ArrayList<String>();
特殊格式一:ArrayList list1 = new ArrayList<String>();
ArrayList<Object> list2 = list1;
这两种写法编译器不会报错。
(4)在创建数组时,数组中的元素不能使用参数化类型,因为数组本身就限定了存放在数组中的元素的类型,即数组中只能存放相同类型的元素。
如:ArrayList<String>[] list = new ArrayList<String>[10];
(5)泛型通配符 ?
应用:自定义一个方法,打印出任意参数化类型的集合中的所有数据。
错误方式:
public void printCollection(Collection<Object> colls){
for(Object obj : colls)
System.out.println(obj);
colls.add("string"); //正确
colls = new HashSet<Date>(); //错误
}
分析:Collection<Object> 中的 Object 只是说集合中的实例对象中的方法接收的参数是 Object 型的。Collection<Object> 是一种具体类型,new HashSet<Date>() 也是一种具体类型,两者不兼容。这个相当于 Collection<Object> colls = new HashSet<Date>() 。
正确方式:
public void printCollection(Collection<?> colls){ //Collection<?>(发音为:"collection of unknown")
for(Object obj : colls)
System.out.println(obj);
colls.add("string"); //错误
colls.size(); //正确
colls = new HashSet<Date>(); //正确
}
分析:colls.add("string"); 错误,是因为不知道未来匹配的就一定是 String 类型,可能是其他类型,所以有不确定因素。colls.size(); 正确,该方法与泛型无关。
有限制的通配符:
限定通配符的上边界:类型变量是Number及其子类类
正确:Vector<?extends Number> x = new Vector<Integer>();
错误:Vector<?extends Number> x = new Vector<String>();
限定通配符的下边界:型变量是Number及其父类
正确:Vector<?super Integer> x = new Vector<Number>();
错误:Vector<?super Integer> x = new Vector<Byte>();
问题:以下代码行正确么?
public void add(List<? extends String> list){
list.add("abc");
}
答:方法声明本身没错,但是在在使用 add 方法时会报错。关于限定型通配符的应用确实没搞懂原理,不知道错误的根本原因。但该处的泛型限定改为带下限<? super String>的定义方式就不会报错了。
有这么一个总结:使用 ? 通配符主要用于引用对象,使用了 ? 通配符,就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。但是在上面问题中若 List<? super String> 又没错,又不符合。
(6)泛型在集合中的应用
其实泛型主要操作在于集合,若能掌握泛型在集合中的应用,应付开发基本没问题。下面以对 Map 集合的操作为例。
@Test
public void test(){
Map<Integer,String> map=new LinkedHashMap<Integer,String>();
map.put(1, "aaa");
map.put(2, "bbb");
map.put(3, "ccc");
//传统while循环,keyset / entryset
Set <Map.Entry<Integer, String>> entrySet = map.entrySet();
Iterator <Map.Entry<Integer, String>> it = entrySet.iterator();
while(it.hasNext()){
Map.Entry<Integer, String> entry= it.next();
int key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"="+value);
}
//增强for(重点),keyset / entryset
for(Map.Entry<Integer, String> entry : map.entrySet()){
int key = entry.getKey();
String value = entry.getValue();
}
}
3、自定义泛型
交换数组中两个元素位置的反映定义方法:
public static <E> void swap(E[] arr, int i, int j){
E temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
swap(new byte[10], 3, 5); //参数错误
swap(new Number[10], 3, 5); //正确
分析:传入原始类型数组错误,对于引用型的数组则正确。所以只有引用类型才能作为泛型方法的实际参数。同 add() 方法一样,在传入基本数据类型进行测试没有问题,那是因为 JDK1.5 的新特性对于基本数据类型的自动装箱和拆箱特性。但若向其中传入基本类型数组时,接收的确是该数组的引用地址值,因为编译器不会对基本数据类型数组(new byte[10])进行自动拆箱和装箱,数组本身就是一个对象。且泛型的定义只对引用类型起作用。
也可以这样自定义泛型方法:
<T extends Serializable & Cloneable> void method(){ ...... }
对于异常类泛型的定义:
public <T extends Exception> void sayHello() throws T{
try{
} catch(Exception e){
throw (T)e;
}
}
普通方法、构造方法、静态方法以及类都可以使用泛型。
静态方法泛型的定义,只能在方法体声明上,不能使用类定义的泛型,静态优先于对象加载到内存中。
需求:定义一个方法,把任意参数类型的一个集合中的数据安全的拷贝到相印类型的数组中。
public static <T> void copy(Collection<T> dest, T[] src){
for (int i = 0; i < src.length; i++) {
dest.add(src[i]);
}
}
4、通过反射获得泛型的实际类型变量(参数)。
如下面这个类,类中方法的带泛型参数进行了实例化,通过反射来获取该实例化类型参数。
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
public class GenericQuestion {
/**
* @param args
* @throws Exception
*/
@Test
public void test() throws Exception {
ArrayList<String> collection1 = new ArrayList<String>();
Collection<? extends Number> collection2 = new ArrayList<Byte>();
getCollectionTypeOfParameter(collection1);
getCollectionTypeOfParameter(collection2);
}
//用反射方式获取参数列表中类型变量
public void getCollectionTypeOfParameter(Collection<?> src)
throws Exception{
TypeVariable<?>[] typeVariable = src.getClass().getTypeParameters();
//获取引用的通用声明(getGenericDeclaration)的类型变量(API中对其声明的类型变量)
System.out.println(typeVariable[0].getGenericDeclaration());
//获取引用的类型
System.out.println(typeVariable[0].getName());
//获取类型变量的上边界,上边界一般为Object(除非<? extends Number>,则为Number)
Type[] type = typeVariable[0].getBounds();
System.out.println(type[0]);
//获取方法的原始参数类及其类型变量
Type[] types = GenericQuestion.class.getMethod
("getCollectionTypeOfParameter", Collection.class).getGenericParameterTypes();
ParameterizedType typeOfParameter = (ParameterizedType)types[0];
System.out.println(typeOfParameter.getRawType()); //原始参数类型
System.out.println(typeOfParameter.getActualTypeArguments()[0]);//类型参数
}
}
分析:Java中的泛型是用于编译器执行类型检查和类型推断的,在正式运行生成原始类的字节码时会用擦除技术抹去泛型定义,所以不可能实现获取引用的实例化类型参数。不过还是要提问哈,这种方式能实现么?即上述方法中如果传入的引用类型是ArrayList<String>,能否就得到原始类 ArrayList 和类型变量 String 呢?