一、什么是泛型?List<String> list = new ArrayList<String>();这里的 <String> 就是常说的“泛型”,这一句代码表示:这个list只能用来存放 String,当你遍历这个list的时候,不用作任何检查,取出来的一定是 String 类型的对象。
二、为什么需要泛型?
要知道这个问题的答案,首先得知道“泛型究竟干了什么”。
List这个接口,其实先于泛型已经存在了,那时的代码是这样的:
List list = new ArrayList();
当你从这个list中取出一个元素的时候,一般需要用 instanceof 来检查它到底是不是你需要的类型,然后用强制类型转换来得到你要的类型的变量:
List list = getListMethod();
for(int i=0; i<list.size(); i++) {
Object item = list.get(i);
if( item instanceof String ) {
String str = (String)item;
}
}
所以,类型检查的工作靠我们这些未来的程序猿来做。
更准确的说法是:类型检查的工作一直保留到了程序运行的时候来做。
而使用了泛型以后,就不需要担心这件事了:
List<String> list = getListMethod();
for(int i=0; i<list.size(); i++) {
String str = list.get(i);
}
这个“类型检查”的工作既然存在,那么就一定要做,——你没有做,那是谁替你做了呢?
答案是:编译器,编译器在编译泛型代码的时候,替你做了类型检查。
大部分人可能都同意这样的说法:对于程序中可能存在的错误,早发现比晚发现好,测试时发现比被用户发现好,编译时发现比运行时发现好。
错误越早发现,纠正错误的成本就越低。
编译器懂得“类型”吗?
答案是:编译器懂得一个变量在代码中“声明”的类型。
看这句变量声明的代码: Number n = Integer.valueOf(123);
编译器只知道变量n的类型是Number,而不知道“其具体类型是Integer”。这里Number就是变量n“声明”的类型。
而基于被声明的类型,编译器允许你进行合法的赋值: n = Double.valueOf(456.0);
编译器也知道这样的赋值不合法: n = "abc";
所以要编译器做类型检查,就得保证这个被检查的类型是声明的类型,换句话说,编译器只会检查声明的类型而不会检查具体(实际)的类型。
除了变量声明,还有两种类型声明是编译器所“知道”的:方法的参数类型,和方法的返回类型。
在泛型被使用之前,Java缺乏的就是在这几种声明中对类型做更详细更高级的描述的语法。
三、泛型的检查是怎样工作的?
例如:
List<String> list = new ArrayList<String>();
list.add("abc");
编译器知道list的类型是String,在所有使用list的代码被编译的时候,编译器会根据 List 接口中的方法中所写的参数类型或返回类型的声明,对其所操作的变量的声明类型做逐一检查。
编译之后,泛型的信息就被编译器“擦掉”了,所以和下面两句代码编译以后是没有什么区别的:
List<String> list = new ArrayList<String>();
List list = new ArrayList();
这两者的区别仅在于编译的过程。
这就保证了编译后代码的向前兼容。
四、泛型的特点,泛型 vs 数组
泛型与数组是有些合不来的,所以如果可能的话,尽量避免泛型与数组一起使用。比如下面的代码,是不合法的:
E[] arr = new E[10];
这里与数组比较,主要提一提泛型的两条特点,或者说,两个与数组截然相反的地方:
1、数组承袭了其类型的继承关系,而泛型没有。
就是说,假如类型B是类型A的子类型,那么数组类型B[]也是数组类型A[]的子类型!
这种糟糕的继承关系为数组逃避编译器的类型检查提供了可能:
Number[] arr = new Integer[10];
arr[0] = Double.valueOf(123.0); // <- 编译器无法发现的类型错误
而泛型没有这种关系,假如类型B是类型A的子类型,List<B> 和 List<A> 是两种不相干的泛型,下面的声明通不过编译器的检查:
List<Number> list = new ArrayList<Integer>();
编译器会提示你这里的类型有问题。
2、泛型的类型检查作用于编译时,而运行时没有泛型信息,更谈不上检查;数组恰恰相反,数组的类型信息一直到运行时都一直存在,并且其类型检查作用于运行时,如果出现了错误的类型操作,它会抛出一个异 常。
运行时的类型检查,不仅增加了运行时的负担,而且就像前面提到的,编译时发现错误总比运行时发现的好。
这时你再回头去看,应该就明白为什么 E[] arr = new E[10]; 这样的代码是不合法的,泛型要履行它的职责,必然与数组是合不来的。
五、小结
泛型是这样一种工具,使用它来让编译器为你作类型检查。
在可能的情况下,应该尽量(正确)使用泛型,以达到类型安全的目的。
应该优先使用泛型的List,而不是数组。ArrayList就是一个用数组支持的List,它的效率与数组相同,提供了更为便捷的方法,并且,它确保了编译时的类型检查。
绝大部分情况下,不要使用坯型,因为使用坯型就放弃了泛型为你提供的所有保障,使代码中可能存在类型安全的隐患。
(java 5 之后)
编译器会对坯型的使用作出警告,如果你因为特别的原因使用了坯型,那么用注解 @SuppressWarnings("rawtypes") 来消灭警告。
编译器还会对未经检查(instanceof)的强制类型转换作出警告,如果你100%确定这个类型转换没有问题,那么用注解 @SuppressWarnings("unchecked") 来消灭警告。
|