一个Bug在JDK中居然被修复了十年,这是怎么回事呢?

摘要:你敢相信么一个简单的Bug,JDK 居然花了十年时间才修改完成。赶快来看看到底是个什么样的 Bug?
问题现象 今天偶然看到了一个 JDK 的 Bug,给大家分享一下。 假设现在有如下的代码: List<String> list = new ArrayList<>(); list.add("1"); Object[] array = list.toArray(); array[0] = 1; System.out.println(Arrays.toString(array)); 上面的代码是可以正常支执行的,如下图所示: 修改代码为如下代码: List<String> list = Arrays.asList("1"); Object[] array = list.toArray(); array[0] = 1; System.out.println(Arrays.toString(array)); 再次执行代码,结果就会抛出 ArrayStoreException 异常,这个异常表明这里并不能把一个 Integer 类型的对象存放到这个数组里面。如下图所示: 查看 Arrays 的静态内部类 ArrayList 的 toArray() 方法的返回值就是 Object[] 类型的,如下图所示: 这里就会引发一个疑问: 为啥使用 java.lang.util.ArrayList 代码就可以正常运行?但是使用 Arrays 的静态内部类 ArrayList 就会报错了? 原因分析 首先看下 java.lang.util.ArrayList 类的 toArray() 方法的实现逻辑: 从上面可以看出 toArray() 方法是拷贝了一个 ArrayList 内部的数组对象,然后返回的。而 elementData 这个数组在实际初始化的时候,就是 new 了 Object 类型的数组。如下图所示: 那么经过拷贝之后返回的还是一个实际类型为Object 类型的数组。既然这里是一个 Object 类型的数组,那么往里面放一个 Integer 类型的数据是合法的,因为 Object 是 Integer 类型的父类。 然后再看下 Arrays 的静态内部类 ArrayList 的 toArray() 方法的实现逻辑。这里返回的是 a 这个数组的一个克隆。如下图所示: 而这个 a 数组声明的类型是 E[],根据泛型擦除后的原则,这里实际上声明的类型也变成了 Object[]。 如下图所示: 那接下来再看看 a 实际的类型是什么? 由于 Arrays 的静态内部类 ArrayList 的构造函数是包级访问的,因此只能通过 Arrays.asList() 静态方法来构造一个这个对象。如下图所示: 而 Arrays.asList() 方法的签名是变长参数类型,这个是 Java 的一个语法糖,实际对应的是一个数组,泛型擦除后就变成了 Object[] 类型。如下图所示: 而在代码实际调用处,实际上会 new 一个 String 类型的数组,也就是说 「a 的实际类型是一个 String 类型的数组」。 那么 a 调用了 clone() 方法之后返回的类型也是一个 String 类型的数组,克隆嘛,类型一样才叫克隆。如下图所示: 经过上面的分析,答案就呼之欲出了。a 的实际类型是一个 String 类型的数组,那么往这个数组里面放一个 Integer 类型的对象那肯定是要报错的。等效代码如下图所示: 为什么是个Bug ? 查看 Collection 接口的方法签名,方法声明明确是要返回的是一个 Object[] 类型的数组,因为方法明确声明了返回的是一个 Object[] 类型的数组,但是实际上在获取到了这个返回值后把它当作一个 Object[] 类型的数组使用某些情况下是不满足语义的。 同时这里要注意一下,返回的这个数组要是一个 「安全」的数组,安全的意思就是「集合本身不能持有对返回的数组的引用」,即使集合的内部是用数组实现的,也不能直接把这个内部的数组直接返回。这就是为什么上面两个 toArray() 方法的实现要么是把原有的数组复制了一份,要么是克隆了一份,本质上都是新建了一个数组。如下图所示: 在 OpenJDK 的 BugList 官网上很早就有人提出这个问题了,从时间上看至少在 2005 年就已经发现这个 Bug 了,这个 Bug 真正被解决是在 2015 年的时候,整整隔了 10 年时间。
阅读全文