A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© fzy0723 中级黑马   /  2015-12-20 12:55  /  1070 人查看  /  11 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

一、什么是泛型?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") 来消灭警告。

评分

参与人数 2黑马币 +6 收起 理由
荆浩_jh + 2 很给力!
张研老师 + 4 赞一个!

查看全部评分

11 个回复

倒序浏览
说的很详细 受教受教
回复 使用道具 举报
赞一个。
回复 使用道具 举报
必须赞一个
回复 使用道具 举报
不错不错学到了很多
回复 使用道具 举报
nice!!!list取出的一定是String的
回复 使用道具 举报
thanks for sharing so important infotmation!
回复 使用道具 举报
董钊 中级黑马 2015-12-21 22:45:36
8#
好牛的样子   受教了
回复 使用道具 举报
Mr.Cai 中级黑马 2015-12-21 22:45:53
9#
666,总结的不错
回复 使用道具 举报
赞赞赞!
回复 使用道具 举报
其实我简单理解为, 去黄, 确定类型, 运行问题提前到编译期
回复 使用道具 举报
学习的不错,很透彻;
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马