黑马程序员技术交流社区

标题: 关于泛型 [打印本页]

作者: fzy0723    时间: 2015-12-20 12:55
标题: 关于泛型
一、什么是泛型?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") 来消灭警告。


作者: sunpeijie    时间: 2015-12-20 14:07
说的很详细 受教受教
作者: 孜孜不倦    时间: 2015-12-20 14:44
赞一个。
作者: 吕振廷    时间: 2015-12-20 18:41
必须赞一个
作者: 王冀仁    时间: 2015-12-21 09:08
不错不错学到了很多
作者: anuo    时间: 2015-12-21 10:41
nice!!!list取出的一定是String的
作者: 499087476    时间: 2015-12-21 20:47
thanks for sharing so important infotmation!
作者: 董钊    时间: 2015-12-21 22:45
好牛的样子   受教了
作者: Mr.Cai    时间: 2015-12-21 22:45
666,总结的不错
作者: 荆浩_jh    时间: 2015-12-21 22:55
赞赞赞!
作者: 迷茫不堪的年纪    时间: 2015-12-22 00:01
其实我简单理解为, 去黄, 确定类型, 运行问题提前到编译期
作者: 石三伢子1    时间: 2015-12-22 10:38
学习的不错,很透彻;




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2