解读王垠博客“一道 Java 面试题”

       偶然拜读IT界知名大佬王垠老师的博客,发现一个有意思的题目:

1 // 这段代码里面到底哪一行错了?为什么?
2 // 原文:http://www.yinwang.org/blog-cn/2020/02/13/java-type-system
3 public static void f() {
4     String[] a = new String[2];
5     Object[] b = a;
6     a[0] = "hi";
7     b[1] = Integer.valueOf(42);
8 }

       虽然小菜才疏学浅,但本着学习交流的态度,写下此篇文章来分析一下这个问题。

 

       首先我们要读懂每一行代码在做什么:

      

       String[] a = new String[2]; 定义一个字符串类型的数组a,并初始化。

       Object[] b = a; 定义一个对象类型的数组b,并将字符串类型数组a赋值给b。

       a[0] = "hi"; 使用变量a访问数组中的第一个元素,赋值。

       b[1] = Integer.valueOf(42); 使用变量b访问数组中的第二个元素,赋值。

 

       只有简单的四行代码,相信读者都可以看的懂。

       先不考虑太多,直接执行一下代码,编译通过,运行报错:java.lang.ArrayStoreException: java.lang.Integer。

       错误提示我们第四行代码有问题,不可以将整型数据存储到数组b中,而b是一个Object类型的数组,编译通过,却无法赋值。

       分析一下原因,数组b的引用指向数组a,我们操作数组b,实际在内存中,访问的应该是数组a,而数组a是一个字符串类型数组,整个过程中,并不存在Object类型的数组,仅有一个字符串类型的数组在内存中被创建,如图:

       变量a和变量b只不过是门面,通过这两道门,到达的是同一个房间。只不过a门只允许String类型通过,而b门没有任何限制。

       因此,假如我们写下a[0] = Integer.valueOf(42);,编译器立刻会发现错误,提示类型错误,而b[1] = Integer.valueOf(42);的写法是符合规则的,但由于实际数据结构是String数组,所以运行肯定无法通过。

       为什么会这样?出现这种问题的根本原因,在于Object[] b = a;,严格来说,这种语法是错误的,但是在JDK规范中却被认可。

       为什么说是错误的?面向对象中的继承我们再熟悉不过了,子类完全具有父类的能力,子类可以退化成为父类。

       单说String的确是Object的子类,完全符合规则,但数组是另一回事,本例中String数组仅仅能容纳String类型的元素,而Object数组可以容纳任意类型的元素,String数组并非完全具有Object数组的能力。

       从另一个角度看,无论是String[] a还是Object[] b,这两种写法中的变量a和变量b,仅仅能决定指针的指向(引用哪个具体的数组),而无法控制数组内的元素,只能整体操作,而数组必然要涉及某个元素的部分操作,这就造成数组内部数据结构的“逸出”,必然会出现问题。

综上,数组之间的抽象是错误的,数组之间没有直接的继承的能力,不属于面向对象继承的讨论范畴。

       实际编写代码时,不必过分纠结这个问题,尽量不使用这种危险的操作,而是用更加优雅的方式去实现:

1 // 这样就能很好的发现错误,避免给自己挖坑
2 public static void f() {
3     String[] a = new String[2];
4     Object b = a;  //数组本身也是对象
5     a[0] = "hi";
6     ((String[]) b)[1] = Integer.valueOf(42);
7 }

 

posted @ 2020-11-07 22:17  杨元  阅读(992)  评论(1编辑  收藏  举报