J006-[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,在这里您可以看到我的代码分享,关注我的最新动态。
更多文章:
参考资料:
- 《Java 核心技术》
- 《Effective Java》
- 《疯狂 Java 讲义》
- Java中,数组为什么要设计为协变?
- 弱类型、强类型、动态类型、静态类型语言的区别是什么?
- How to create a generic array in Java? 如何在 Java 中创建通用数组?
版权声明:
该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:2531574300,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。版权所有 ©心如止水 保留一切权利。
心如止水
网友评论