今天在学习 ArrayList
源码的时候发现了这么一句注释,即:
c.toArray might (incorrectly) not return Object[] (see 6260652)
这句话的意思是 Collection
集合类型的 toArray()
方法虽然声明返回值类型是 Object[]
,但是具体调用时还真不一定就返回 Onject[]
类型,也有可能是其他的类型,这还要取决于你 c
的实际类型,使用不当还会抛出异常。这样讲可能会很懵比,下面我将会详细讲解到底为什么,现在我们先来看看 Collection
中的 toArray()
声明,让你对这个方法先有个大概的印象。
public Object[] toArray(); // 声明返回值类型为Object[]
那么什么情况会出现上面的bug呢?我们先来看看下面两个例子:
1、没有抛异常的情况
// 声明一个ArrayList集合,泛型为String类型 List<String> list = new ArrayList<>(); // 添加一个元素 list.add("list"); // 将上面的集合转换为对象数组 Object[] listArray = list.toArray(); ................ 1 // 输出listArray的类型,输出class [Ljava.lang.Object; System.out.println(listArray.getClass()); // 往listArray赋值一个Onject类型的对象 listArray[0] = new Object();
2、抛异常的情况
// 同一创建一个列表,但是现在是通过Arrays工具类来创建,创建的列表类型为Arrays的内部类ArrayList类型 List<String> asList = Arrays.asList("string"); // 转换为对象数组 Object[] asListArray = asList.toArray();.............. 2 // 输出转换后元素类型,将输出class [Ljava.lang.String; System.out.println(asListArray.getClass()); // 往对象数组中添加Object类型对象,会报错java.lang.ArrayStoreException asListArray[0] = new Object();
上面第一种情况是通过 new ArrayList()
方式创建的 java.util.ArrayList
类型,第二种方式是使用 Arrays.asList()
方式创建的 java.util.Arrays$ArrayList
的类型,两个类型名都是 ArrayList
,但实现方式确实不同的。那为什么会报错呢?归根到底就是 toArray()
这个方法的实现方式不同导致的。我们分别先看下 java.util.ArrayList
类的 toArray()
和 java.util.Arrays$ArrayList
的 toArray()
的实现方式:
java.util.ArrayList
public Object[] toArray() { // 调用Arrays工具类进行数组拷贝 return Arrays.copyOf(elementData, size);...............1 }
Arrays.copyOf()
public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass());.................2 } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { // 在创建新数组对象之前会先对传入的数据类型进行判定 @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
下面是 java.util.Arrays$ArrayList
的实现
private final E[] a; @Override public Object[] toArray() { return a.clone(); }
从上面可以看出,在 java.util.ArrayList
中将会调用 Arrays
的 copyOf()
方法,传入一个 newType
进行类型判断, newType
的值为 original.getClass()
,由于 original.getClass()
返回的类型为 Object[]
(具体看 ArrayList
的源码可知),所以调用 toArray()
之后将返回一个 Object[]
类型数组,所以往 listArray
变量里边丢一个 Object
类型的对象当然不会报错。
再看看 java.util.Arrays$ArrayList
的实现,可以看出数据类型定义为泛型 E
, E
的具体类型将根据你传入的实际类型决定,由于你传入了 "string"
,所以实际类型就是 String[]
,当然调用 a.clone()
之后还是一样返回 String[]
类型,只不过是这里做了一个向上转型,将 String[]
类型转为 Object[]
类型返回罢了,但是注意,虽然返回的 引用
为 Object[]
,但实际的类型还是 String[]
,当你往一个 引用
类型和实际类型不匹配的对象中添加元素时,就是报错。不服?下面举个栗子:
// 数组strings为String[]类型 String[] strings = { "a", "b" }; // 向上转型为Object[]类型,那么这个objects就属于引用类型为Object[],而实际类型为String[] Object[] objects = strings; // 添加一个Object类型变量,就报错啦! objects[0] = new Object();//! java.lang.ArrayStoreException
为了加深理解,我们来总结下java中的向上转型和向下转型的区别。我们都知道我们可以通过注入 Father fa = new Son()
的方式进行声明,仅为 Father
类型为 Son
类型的父类,即发生向上转型,向上转型在java中是自动完成的,不需要进行强制转换,不会抛出异常。向下转型分为两种情况,下面结合代码演示:
// 向上转型 Father fa = new Son(); Father fafa = new Father(); // 向下转型(不会报错) Son son = (Son) fa;.................1 // 向下转型,报错了java.lang.ClassCastException Son sonson = (Son) fafa;.......................2
可以发现 1
处不会报错, 2
处却报错了,因为 1
处 fa
变量的实际类型是 Son
,引用类型为 Father
,向下转换取决于实际类型而不取决于引用类型,比如 fafa
这个变量的实际类型就是其本身 Father
,在java中,父类默认是不能强制转换为子类的。
首先最重要有以下几点:
1、Java中数组集合向上转型之后,不能往数组集合中添加引用类型(即父类型)的对象,而应该添加实际类型的对象,比如说``Father[] father = son[] ,你就不能往
father 中添加
Father 类型了,而应该是
Son`
2、Java中向上转型是默认允许的,但是向下转型可能会抛出错误,得小心使用!
3、要小心采用 Arrays.asList()
创建的集合类型不是 java.util.ArrayList
,而是 java.util.Arrays$ArrayList
,两个类的很多方法实现方式也不一样。
谢谢阅读,欢迎评论区交流!