J006-[Java菜鸟系列] 泛型数组的问题是「体制」问题菜鸟:Java为什么不支持泛型数组?老湿:上一次提到,为了兼容性,泛型只存在于编译器中,在虚拟机中,泛型参数被擦除,泛型变为原始类型。这被称为类型擦除,由此会导致一系列的问题,这些内容在任何一本Java的书上都能找到。但是,书中解释的确不好理解,尤其是泛型和数组混合使用的问题。为啥这么难理解?很普遍的一种解释以下内容摘自《疯狂 Java 讲义(第三版)》(P354) 。《Java 核心技术 卷I》 以及很多文章也使用了类似案例和解释。书中案例如下: public static void main(String[] args) { //下面代码实际上是不允许的 List<String>[] Isa = new List<String>[10]; Object[] oa = (Object[])Isa; List<String>[] oa =Isa; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); String s = Isa[1].get(0); //① }书中解释如下:如果代码是合法的,势必在①处引发运行时异常,这就违背了 Java 泛型的设计原则。重点问题没有说这种解释是说得通的。但第一次看到的时候,我不知道他在讲什么。因为,“这就违背了 Java 泛型的设计原则"这句话实在是太哲学了,如果不讲清楚,原则被违背的细节,那么是理解不了的。说难听一点,这就是典型的"正确的废话”。就好比你在路上开着车被交警给拦下来了,然后发生以下对话。交警:你刚刚时速 50KM/h ,超速了,扣三分,罚款 200。斯基:这是高速路啊,为啥别的高速都限速 100KM/h,你们这里限速 50KM/h ?为啥你们这么特殊呢?交警:因为这违反了《交通法》原则。斯基:。。。。。。。这他娘的不是废话吗?老资知道犯法了,问你犯法为何,你说犯法是因为违反了法律的原则。此时此刻 斯基 是这么想的深深地误解了相信很多人看到这种解释的时候,都会有这样一个理解:哦哦,这是因为"泛型"设计的太烂,前天学 C++ 的小丽就告诉我,Java是"伪泛型",老湿还给Java洗地,今天看你怎么洗地。呸!呸!呸!呸!呸!本人水平有限,不敢这么说,但是《Effective Java》上面也是这么说的,我只是用原生中文解释一下。以下为原文:你可能认为,这意味着泛型是有缺陷的, 但实际上可以说数组才是有缺陷的。-《Effective Java》中文版 P105 (第25条:列表优先于数组)Java的原则是什么首先问一个问题,Java属于什么类型语言?答案很明确,Java是强类型(类型安全),类型检查是静态。什么是强类型?强弱类型的区别在于对待"类型错误"的态度。强类型指的是,不容忍隐式类型转换,而且"类型错误"不能被捕获,一旦出错程序立即停止运行。弱类型中能容忍隐式转换,对于"类型错误"也宽松,你可以 try catch 就了事了,不用非要停止运行。这就好比,小商小贩卖点东西在美帝是没问题的,有点纠纷那属于民事诉讼,甚至私了就行了;到天朝性质变了,那叫投机倒把,最高可以是死罪。什么是静态?动态静态的区别在于检查"类型错误"的时机。静态类型是指的,对于"类型错误",尽量在编译时期就查出来;动态类型在编译的时候是不查的,实际运行之后才查。这就好比,我国工商曾是"审批制",办事先要一摞证明;其他一些国家是"备案制"为主,也就是先相信大家是好同志,有问题之后再说。金无足赤1.体制有很多种,不能泛泛的说谁好谁坏,只有在具体情况下谁更合适。2.“鱼和熊掌不可兼得”。“方便灵活"和"安全可靠"往往就是一对矛盾体。3.“体制"不是非黑即白的。这就好比,我国和朝鲜都是社会主义国家,但是体制一样吗?明显不一样的。有 Java 特色的强类型体制以下内容无可靠信源,容易出错误,谨慎参考Object 的体制从图上你可以看到,其实 Java 并不是完全的"强类型和静态的”。为什么呢?Object 作为根类是可以容纳任何其他引用类型的,而且这个过程是"隐式转换"的,在这里,Java为了灵活性牺牲了一定的安全性,所以不那么"强类型”。但是,为了保住颜面,只要你反过来赋值,Java 打死也不会都帮你"隐式转换"的,必须要你手动"强制转型",“强制转型"是运行时的,所以也不那么"静态”。这样虽然不安全,但是很方便。为什么不安全?说到底,你还是不知道 Object 里面到底是什么玩意,但是起码背锅的就不是 Java 了,因为是你手动转换的。 Object s = new StringBuffer(“Test.txt”); String fileName = (String)s;虽然,Object 和 隐式转型 牺牲了"安全性"提升了"灵活性",但是你能说 “Java 不是强类型"吗,这叫做"有Java特色的强类型体制”。还没懂的,你可以看下面这个例子。改革开放初期,朝鲜同志们批评我们是犯了修正主义错误,邓小平同志是这么回应的。社会主义也可以搞市场经济 - 邓小平泛型在设计 集合类 时候,这种体制出现了弊端,由于集合类的目的就是存储各种对象,所以强制转型成了家常便饭,这个"安全性"问题被放大了。Java SE 5 上进行了大胆的创新,引入泛型<>,泛型是企图在编译期间就检查类型,所以弥补了 Object 留下的大坑。Java 泛型的设计原则回到之前那个问题,“Java 泛型的设计原则"是什么?原则就是"在使用集合的场景下,尽可能的增加安全性”。“为什么泛型和数组一起用安全性就被损害了”?,解释这个终极问题之前,我们先看看数组的体制。Object[] 的体制先来看一下这个语句,你认为可以正常运行吗?String[] Isa = new String[2];Object[] oa =Isa;答案是"可以的",这也被称为"协变性",之所以这么搞,还是为了"灵活性"而牺牲"安全性"。这种搞法解决了 Java 初期泛型还没有诞生时的很多程序设计问题。在之前的时候,虽然Object的强制转型很蛋疼,一不小心就有错误,但是起码还是可以写对的吧。 Object s = new StringBuffer(“Test.txt”); String fileName = (String)s; //错了 StringBuffer fileNameBuffer = (StringBuffer)s;//回去一看,发觉是StringBuffer,又改对了 但是如果 Object[] 这么搞了,那么就完蛋了。 public static void main(String[] args) { String[] Isa = new String[2]; Object[] oa = Isa; oa[1] = new StringBuffer(“MyString”); String s = Isa[1]; //错 StringBuffer sB = (StringBuffer)Isa[1]; //错 StringBuffer sBB = Isa[1]; //错 }只要先用点小花招,把 String[] 赋值给 Object[] ,那么就可以绕过编译检查。如果 只是 Object那么问题好解决,用强制转型就可以。但是,Object 和 [] 在一起就完蛋了,数组认为里面的东西是String (其实已经被猪队友Object调包了),显然 String 无法强制转型成 StringBuffer ,结果就怎么做都是错的。咦,你看着是不是有点熟?对,这就是之前的"很普遍的一种解释",虽然这里把创建泛型数组给换成创建StringBuffer了,但是依然是不对的。所以泛型固然有问题,但是更大的问题在于Object[]。为什么这个问题没人管?因为"积重难返",当泛型出现的时候,Object[] 的很多功能其实可以被替代,这个妥协的设计应该是可以退休的。但是由于底层的各种代码都是用这个机制写的,根本就不可能重构,固然就留下来了。泛型和数组水火不容的终极答案因为Object[]这东西本身安全性问题很大。如果不和Object[]在一起,泛型就可以保证在大量对象读写的集合类中始终保持高安全性,在编译时期就可以检查出类型的问题;但是只要和Object[]在一起,那么编译的检查就可以被绕过,安全性就又被损害了。“泛型"就是为了解决安全性问题的,Object[]这么不安全的家伙,和"泛型"混在一起没有任何好处。泛型和数组的和谐相处之道已经是骑虎难下,但两边利益都很大,那么终极杀招就出来了,一国两制。具体实施细节是这样的,数组建立的时候需要知道类型,那么就在数组建立时检查它,一旦发现类型为泛型,那就直接拒绝。非要用还是有办法的既然检查是在"数组建立时"进行的,那么只要"数组建立时"没有泛型,就可以成功绕过检查。其实这个和 Object 问题的解决之道是一样的,那就是强制转型。在网上我找了个例子,放在这下面了。@SuppressWarnings(“unchecked”) static final int SIZE = 100; static Pair<Integer>[] gia;// @SuppressWarnings(“unchecked”) public static void main(String[] args) { // Compiles; produces ClassCastException: //! gia = (Pair<Integer>[])new Object[SIZE]; // Runtime type is the raw (erased) type: gia = (Pair<Integer>[])new Pair[SIZE]; System.out.println(gia.getClass().getSimpleName()); //安全性依然还是那样,依然可以偷换成功。不过由于是你自己"强制转型"的,这锅Java不背罢了 Object[] go = gia; go[1] = new Pair<Double>(); gia[0] = new Pair<>(); //! gia[1] = new Object(); // Compile-time error // Discovers type mismatch at compile time: //! gia[2] = new Pair<Double>(); Pair<Integer> g = gia[0]; }这么绕过检查机制,安全性并不会有提高,依然可以偷换成功。不过由于是你自己"强制转型"的,这锅 Java 不背罢了。虽然一国两制,不能随便去香港,但是你可以办通行证的。总结之所以两者不能一起用是因为:体制是有冲突的,如果一起用,就违背了"设计出泛型来提高集合类提高安全性"的初衷。所以,Java的设计者们英明的实行了一国两制,让数组不接受泛型,成功的解决了体制不一致问题。有没有两全其美的办法最安全的方案是用 集合类 代替 数组。因为 集合类 本身就是泛型实现的,他们体制是一样的。如果需要收集参数化类型对象, 只有一种安全而有效的方法: 使用 ArrayList:ArrayList<Pair<String>>。 - 《Java 核心技术 卷I》End心如止水是Java/AHK持续学习者,欢迎您来和我探讨Java/AHK问题 ^_^GitHub欢迎您来访问我的GitHub,在这里您可以看到我的代码分享,关注我的最新动态。更多文章:[Java菜鸟系列] 红警思维看"泛型”[Java菜鸟系列] 协议与回调[Java菜鸟系列] 内部类与lambda表达式参考资料:《Java 核心技术》《Effective Java》《疯狂 Java 讲义》Java中,数组为什么要设计为协变?弱类型、强类型、动态类型、静态类型语言的区别是什么?How to create a generic array in Java? 如何在 Java 中创建通用数组?)版权声明:该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:2531574300,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。