问题示例

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的类型说明,而不是创建范型数组了。

参考

为什么Java不允许创建范型数组的更多相关文章

  1. Java创建boolean型数组

    Java如何声明并初始化一个boolean型的数组? public class Main{ static boolean[] arr1 = new boolean[20]; public static ...

  2. Java数据结构与算法分析-第一章(引论)-Java中的范型<T,E>构件

    一.为什么需要使用范型? 官方的说法是:Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质 ...

  3. Java 中能创建 volatile 数组吗?

    能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不 是整个数组.我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护, 但是如果多个线程同时改变数 ...

  4. C# 利用范型与扩展方法重构代码

    在一些C#代码中常常可以看到 //An Simple Example By Ray Linn class CarCollection :ICollection { IList list; public ...

  5. Java数组协变与范型不变性

    变性是OOP语言不变的大坑,Java的数组协变就是其中的一口老坑.因为最近踩到了,便做一个记录.顺便也提一下范型的变性. 解释数组协变之前,先明确三个相关的概念,协变.不变和逆变. 一.协变.不变.逆 ...

  6. Java范型学习笔记

    对于范型的使用或者说印象只有集合,其他地方即使使用过也不知道,反正就是只停留在List<E> Map<K, V>,最近刚好闲来无事,就找找资料学习一下:下列为个人学习总结,欢迎 ...

  7. Java范型随笔

    最近在帝都好无聊啊, 排遣寂寞就只有让自己不要停下来,不断的思考了 QWQ; 最近做ndk, java有点忘了,突然看到了一些java范型方面的问题, 踌躇了一会, 想着想着,决定还是写个随笔记录下来 ...

  8. Java Comparator的范型类型推导问题

    问题 在项目中,有一处地方需要对日期区间进行排序 我需要以日期区间的开始日为第一优先级,结束日为第二优先级进行排序 代码 我当时写的代码如下: List<Pair<LocalDate, L ...

  9. Java范型

    泛型不用考虑对象的具体类型.优点在于,因为不用考虑对象的具体类型所以可以对一类对象执行一定的相同操作:缺点在于,因为没有考虑对象的具体类型所以就不能使用对象自带的接口函数.泛型的最佳用同是实现容器类. ...

随机推荐

  1. 微服务从nacos配置中心获得配置信息

    一,安装nacos, 略 二,创建父工程和微服务工程 service1, service2,以idea为例 1, new -> project -> Maven -> 填写group ...

  2. Codeforces Round #679 (Div. 2, based on Technocup 2021 Elimination Round 1)

    考场上只做出来四道,第二天一早就写出来了E,蛮绝望的. A Finding Sasuke 水构造 #include <cstdio> #include <algorithm> ...

  3. Linux 系统编程 学习:06-基于socket的网络编程1:有关概念

    Linux 系统编程 学习:006-基于socket的网络编程1:有关概念 背景 上一讲 进程间通信:System V IPC(2)中,我们介绍了System IPC中关于信号量的概念,以及如何使用. ...

  4. [NOIP 2016D2T2/Luogu P1600] 天天爱跑步 (LCA+差分)

    待填坑 Code //Luogu P1600 天天爱跑步 //Apr,4th,2018 //树上差分+LCA #include<iostream> #include<cstdio&g ...

  5. 正式班D23

    2020.11.05星期四 正式班D23 目录 12.3.3 HUP信号 12.3.3 HUP信号 在关闭终端时,终端会收到Linux HUP信号(hangup信号),关闭其所有子进程. 想让进程一直 ...

  6. Python列表lists索引关于字符串小纪

    看的出'字符串列表'中的空格也是计算在内的

  7. 从比心APP源码的成功,分析陪玩系统源码应该如何开发

    提起游戏陪玩系统,相信大家都不陌生.作为一名骨灰级的手游玩家,小编对于陪玩系统源码也有些了解.在互联网络发展愈发迅速的今天,游戏产业在一中领域中脱颖而出,据统计,手机游戏用户已经达到5.29亿,较20 ...

  8. 4G模块与WIFI模块的工作及应用区别

    在物联网行业中,4G模块和wifi模块经经常会被使用,但是由于网络不同,二者的工作原理和场景还是有很大的不同,本篇主要讲讲4G模块和WIFI模块的功能和应用场景 什么是4G模块? 4G模块是基于4G网 ...

  9. CV 履历 格式

    CV 指的是 "Curriculum Vitae" Curriculum vitae 在拉丁语中的意思是"生命的故事" CV 经常被称为 "Resum ...

  10. linux 内核并发同步 2

    信号量semaphore 信号量是一种允许进程进入睡眠的同步机制,信号量是一个计数器,支持两种原语即P 和V操作,也就是down 和up 操作, /* Please don't access any ...