问题示例

List<Integer>[] intListArr = new ArrayList<Integer>[8]; // 编译时报错

能看到这么看似没啥问题的一个简单语句甚至连编译都不会通过,为了能理解这里面的缘由,我们先得了解变型(Variant)的概念

变型(variant)

  • 协变(covariant) 允许在子类出现的地方用超类替换(A feature which allows to substitute a subtype with supertype.)
  • 逆变(contravariant) 允许在超类出现的地方用子类替换(A feature which allows to substitute a supertype with subtype.)
  • 不变(invariant) 以上两种都不适用。

数组中的变型

早期版本的Java不包含泛型(generics,即参数化多态)。在这样的设置下,使数组为“不变”将导致许多有用的多态程序被排除。
例如,考虑一个用于重排(shuffle)数组的函数,或者测试两个数组相等的函数,使用Object的equals方法. 函数的实现并不依赖于数组元素的确切类型,因此可以写一个单独的实现而适用于所有的数组:

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

然而,如果数组类型被处理为“不变”,那么它仅能用于确切为Object[]类型的数组。对于字符串数组等就不能做重排操作了。

所以,Java把数组类型处理为协变。在Java中,String[]是Object[]的子类型。

范型中的变型

当范型最初在jdk1.5被引入的时候,是有意没有把它设计成协变的,我们来看个例子:

List<Dog> dogs = new ArrayList<Dog>(); // Dog的超类是Animal
List<Animal> animals = dogs; // 把dogs的引用赋给animals超类,编译不通过
animals.add(new Cat()); // Cat是超类Animal的另一个实现
Dog dog = dogs.get(0); // 看起来这应该是对的?^^

即使jdk团队修改底层实现让这段程序能通过编译,最终的结果肯定是ClassCastException,这时候我们再来看看范型设计的目的:
Generics add stability to your code by making more of your bugs detectable at compile time.

范型希望能在编译期间就能找到并避免程序潜在的bug,所以根本就不会把范型实现为协变,这样会更加容易出错,也就是我们在很多书中看到对本问题的解释:这就违背了 Java 泛型的设计原则

可能有人会问,通配符不是让范型可以协变或逆变或不变吗?

补充说明:
通配符的能力是可以通过指定一个更严格的界来让范型变得更加特化,使范型可以在上界协变(List<? extends Cat> 是 List<? extends Animal>的子类型)以及在下界逆变。这是一种范型定义的描述,并不是实际使用时对范型的类型实例化,所以通配符丰富了范型的功能,和范型本身是不变(invariant)的特性不冲突。

结论

说了这么多,其实就是想说明一种设计的初衷,数组是协变的,范型是不变的,这两者结合在一起使用那就肯定会出问题,例如:

List<Integer>[] intListArr = new ArrayList<Integer>[8];// 假设范型数组创建成功
List<Long> longList = new ArrayList<Long>();
List<Number>[] numberArr = intListArr; // 协变性保证可以赋值
numberArr[0] = longList;
Integer value = numberArr[0].get(0);

结果就是ClassCastException了。

当然如果代码写成new ArrayList[8]编译是通过了,这时候范型是对变量intListArr的类型说明,而不是创建范型数组了。

参考

最新文章

  1. 【原】IOS合并lib(.a)库的终极可用方法(可用于解决duplicate symbol静态库冲突)
  2. windows8.1下常用编辑器安装配置(emacs/vim/sublime text3)
  3. jquery.validate 基础
  4. jenkins+ant+jmeter搭建持续集成的接口测试平台
  5. Xiph基金会成员:Timothy B. Terriberry
  6. Android NDK 开发(三)--常见错误锦集合Log的使用【转】
  7. 025. asp.net中GridView的排序和过滤
  8. n人比赛,可轮空,比赛轮数和场数
  9. 自定义Camera综述(一般步骤、注意事项、遇到的难题&lt;【内存溢出问题】&gt;、像素参考)
  10. 通过shell脚本实现代码自动化部署
  11. Cannot modify header information - headers already sent by
  12. VS2015 启用“仅我的代码”
  13. Linux主机上发布java web应用
  14. WPF3D学习,立方体的绘制
  15. VMware+Windbg双机调试
  16. JS 设计模式七 -- 模板方法模式
  17. &#127827; DOM常用基础知识点汇总(入门者适用) &#127827;
  18. Python3浮点型(float)运算结果不正确处理办法
  19. js★★★【面向对象的使用方法】*****************★★★★ 相当重要
  20. Eclipse 常用快捷键使用说明

热门文章

  1. Navicat连接Mysql8.0.17出现1251错误 / 或者Navicat Premium出现2059错误
  2. Spring源码知识概览
  3. 我用 Python 撸了一个 plist 图集拆图工具!附上github源码
  4. pytorch训练GAN时的detach()
  5. ubuntu 18.04 安装anaconda
  6. leetcode117:search-rotated-sorted-array
  7. Python基础教程分享,视频教程免费下载!
  8. GraphX 在图数据库 Nebula Graph 的图计算实践
  9. 03 . Vue基础之计算属性,组件基础定义和使用
  10. [MIT6.006] 18. Speeding up Dijkstra 加速Dijkstra算法