在 java.util.ArrayList 源码中:
c.toArray might (incorrectly) not return Object[] (see 6260652) 产生疑惑:
附上 Java Bug 网址: Java Bug Database
, 可以根据关键词或 bug id 查询详细信息
这个 Bug 的描述中可以看出:
原因: Arrays 内部实现的 ArrayList 的 toArray()方法的行为与规范不一致.
代码测试:
- import java.util.*;
- public class Test{
- public static void demo1(){
- System.out.println("this is demo1");
- List<String> list=new ArrayList<>();
- list.add("张三");
- list.add("王五");
- Object[] arr=list.toArray();
- System.out.println(arr.getClass().getCanonicalName());
- arr[0]=new Object();
- Test.printArr(arr);
- /*
- 正常编译, 执行:
- this is demo1
- java.lang.Object[]
- [email protected] 王五
- */
- }
- public static void demo2(){
- System.out.println("this is demo2");
- List<String> list = Arrays.asList("张三", "王五");
- Object[] arr=list.toArray();
- System.out.println(arr.getClass().getCanonicalName());
- arr[0]=new Object();
- Test.printArr(arr);
- /*
- 正常编译
- 执行输出:
- this is demo2
- java.lang.String[]
- Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
- at Test.demo2(Test.java:31)
- at Test.main(Test.java:55)
- */
- }
- public static void demo3() {
- System.out.println("this is demo3");
- Object[] arr = new String[]{"张三", "王五"};
- System.out.println(arr.getClass().getCanonicalName());
- arr[0] = 7;
- Test.printArr(arr);
- /*
- 正常编译
- 执行输出:
- this is demo3
- java.lang.String[]
- Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
- at Test.demo3(Test.java:48)
- at Test.main(Test.java:71)
- */
- }
- public static void printArr(Object[] arr) {
- for (Object o : arr) {
- System.out.print(o + " ");
- }
- System.out.println();
- }
- public static void main(String[]args){
- //Test.demo1();
- //Test.demo2();
- Test.demo3();
- }
- }
输出截图:
分析过程详解:
第一步:
看 java.util.ArrayList 源码:
- public ArrayList(Collection<? extends E> c) {
- elementData = c.toArray();
- size = elementData.length;
- // c.toArray might (incorrectly) not return Object[] (see 6260652)
- if (elementData.getClass() != Object[].class)
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- }
看 java.util.ArrayList, 中 toArray()源码:
- public Object[] toArray() {
- return Arrays.copyOf(elementData, size);
- }
- /**
- * 返回 ArrayList 元素组成的数组
- * @param a 需要存储 list 中元素的数组
- * 若 a.length>= list.size, 则将 list 中的元素按顺序存入 a 中, 然后 a[list.size] = null, a[list.size + 1] 及其后的元素依旧是 a 的元素
- * 否则, 将返回包含 list 所有元素且数组长度等于 list 中元素个数的数组
- * 注意: 若 a 中本来存储有元素, 则 a 会被 list 的元素覆盖, 且 a[list.size] = null
- * @return
- * @throws ArrayStoreException 当 a.getClass() != list 中存储元素的类型时
- * @throws NullPointerException 当 a 为 null 时
- */
- @SuppressWarnings("unchecked")
- public <T> T[] toArray(T[] a) {
- // 若数组 a 的大小 <ArrayList 的元素个数, 则新建一个 T[]数组,
- // 数组大小是 "ArrayList 的元素个数", 并将 "ArrayList" 全部拷贝到新数组中
- if (a.length < size)
- // Make a new array of a's runtime type, but my contents:
- return (T[]) Arrays.copyOf(elementData, size, a.getClass());
- // 若数组 a 的大小>= ArrayList 的元素个数, 则将 ArrayList 的全部元素都拷贝到数组 a 中.
- System.arraycopy(elementData, 0, a, 0, size);
- if (a.length> size)
- a[size] = null;
- return a;
- }
可以看出, 由于 ArrayList 中 elementData 类型为 Object[], 所以调用 copyOf()返回值类型为 Object[].
第二步:
看 Arrays.asList()源码:
- public static <T> List<T> asList(T... a) {
- return new ArrayList<>(a);
- }
仔细阅读官方文档, 你会发现对 asList 方法的描述中有这样一句话:
返回一个由指定数组生成的固定大小的 List.
注意: 参数类型是 T , 根据官方文档的描述, T 是数组元素的 class.
任何类型的对象都有一个 class 属性, 这个属性代表了这个类型本身. 原生数据类型, 比如 int,short,long 等, 是没有这个属性的, 具有 class 属性的是它们所对应的包装类 Integer,Short,Long.
asList 方法的参数必须是对象或者对象数组, 而原生数据类型不是对象. 当传入一个原生数据类型数组时, asList 的真正得到的参数就不是数组中的元素, 而是数组对象本身.(解决方案: 使用包装类数组.)
继续分析:
此时的 ArrayList 并非我们常用的 java.util.ArrayList, 而是 Arrays 的内部类. 它继承自 AbstractList, 自然实现了 Collection 接口, 代码如下:
- private static class ArrayList<E> extends AbstractList<E>
- implements RandomAccess, java.io.Serializable
- {
- private static final long serialVersionUID = -2764017481108945198L;
- private final E[] a;
- ArrayList(E[] array) {
- if (array==null)
- throw new NullPointerException();
- a = array;
- }
- public int size() {
- return a.length;
- }
- ......
- }
可以发现, 这里的 a 不是 Object[], 而是 E[].
a 称为该 ArrayList 的 backed array. 同时构造函数也是直接用 array 给 a 赋值. 这就是问题的所在.
另外, 这个内部类里面并没有 add,remove 方法, 它继承的 AbstractList 类里面有这些方法:
- ublic abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
- .......
- public void add(int index, E element) {
- throw new UnsupportedOperationException();
- }
- public E remove(int index) {
- throw new UnsupportedOperationException();
- }
- ......
abstractList 这个抽象类所定义的 add 和 remove 方法, 仅仅是抛出了一个异常!
如果是想将一个数组转化成一个列表并做增加删除操作的话, 建议代码如下:
- public class Test {
- public static void main(String[] args) {
- String[] myArray = { "张三", "李四", "赵六" };
- List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
- myList.add("王五");
- }
- };
- demo2(测试代码中的):
- public static void demo2(){
- System.out.println("this is demo2");
- List<String> list = Arrays.asList("张三", "王五");
- Object[] arr=list.toArray();
- System.out.println(arr.getClass().getCanonicalName());
- arr[0]=new Object();
- Test.printArr(arr);
- /*
- 正常编译
- 执行输出:
- this is demo2
- java.lang.String[]
- Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
- at Test.demo2(Test.java:31)
- at Test.main(Test.java:55)
- */
- }
上面的抛出异常分析:
asList 方法直接将 String[]数组作为参数传递给 ArrayList 的构造方法, 然后将 String[]直接赋值给内部的 a, 所以 a 的真实类型是 String[], 根据 JLS 规范 String[]的 clone 方法返回的也是 String[]类型. 最终, toArray()方法返回的真实类型是 String[], 此时, 操作 arr[0]=newObject(); 是向数组中添加 Object 对象, 就会报异常的问题了.
Jdk 6260652 Bug 问题是在 2005 年提出的, 现在已经解决了, 使用 toArray(T[] a)避免 Exception 的发生, 所以可能会导致类型不匹配的错误.
小总结:
Arrays.asList()的使用方法:
该方法是将数组转化为 list. 有以下几点需要注意:
1. 该方法不适用于基本数据类型(byte,short,int,long,float,double,boolean)
解决方案: 使用包装类数组, 例子如下:
- public class Test {
- public static void main(String[] args) {
- Integer[] myArray = { 1, 2, 3 };
- List myList = Arrays.asList(myArray);
- System.out.println(myList.size());
- }
- }
2. 该方法将数组与列表链接起来, 当更新其中之一时, 另一个自动更新
3. 不支持 add 和 remove 方法
将数组转化为一个 List 对象, 一般会想到 Arrays.asList()方法, 这个方法会返回一个 ArrayList 类型的对象. 但是用这个对象对列表进行添加删除更新操作, 就会报 UnsupportedOperationException 异常.
原因: 这个 ArrayList 类并非 java.util.ArrayList 类, 而是 Arrays 类的静态内部类!
说明: asList 的返回对象是一个 Arrays 内部类, 并没有实现集合的修改方法. Arrays.asList 体现的是适配器模式, 只是转换接口, 后台的数据仍是数组.
- String[] str = new String[]{
- "张三","王五"
- };
- List list = Arrays.asList(str);
第一种情况: list.add("赵四"); // 运行时异常
第二种情况: str[0] = "大二哈"; //list.get(0)也随着修改.
此类包含用来操作数组 (比如排序和搜索) 的各种方法. 此类还包含一个允许将数组作为列表来查看的静态工厂. 除非特别注明, 否则如果指定数组引用为 null, 则此类中的方法都会抛出 NullPointerException.
来源: http://www.bubuko.com/infodetail-3074140.html