美文网首页Java 杂谈秒懂Java
[Java菜鸟系列] 泛型数组的问题是「体制」问题

[Java菜鸟系列] 泛型数组的问题是「体制」问题

作者: d61f25068828 | 来源:发表于2019-01-10 17:54 被阅读1次

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]; //错

    }
image image

只要先用点小花招,把 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:Arra
yList<Pair<String>>。 - 《Java 核心技术 卷I》

End

心如止水是Java/AHK持续学习者,欢迎您来和我探讨Java/AHK问题 _

GitHub

欢迎您来访问我的GitHub,在这里您可以看到我的代码分享,关注我的最新动态。

更多文章:

参考资料:

版权声明:

该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:2531574300,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。

心如止水

相关文章

网友评论

    本文标题:[Java菜鸟系列] 泛型数组的问题是「体制」问题

    本文链接:https://www.haomeiwen.com/subject/ryaarqtx.html