今天在学习ArrayList
源码的时候发现了这么一句注释,即:java
c.toArray might (incorrectly) not return Object[] (see 6260652)
数组
这句话的意思是Collection
集合类型的toArray()
方法虽然声明返回值类型是Object[]
,可是具体调用时还真不必定就返回Onject[]
类型,也有多是其余的类型,这还要取决于你c
的实际类型,使用不当还会抛出异常。这样讲可能会很懵比,下面我将会详细讲解到底为何,如今咱们先来看看Collection
中的toArray()
声明,让你对这个方法先有个大概的印象。bash
public Object[] toArray(); // 声明返回值类型为Object[]
复制代码
那么什么状况会出现上面的bug呢?咱们先来看看下面两个例子:ide
一、没有抛异常的状况
工具
// 声明一个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();
复制代码
二、抛异常的状况
学习
// 同一建立一个列表,可是如今是经过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()
的实现方式:ui
java.util.ArrayList
spa
public Object[] toArray() {
// 调用Arrays工具类进行数组拷贝
return Arrays.copyOf(elementData, size);...............1
}
复制代码
Arrays.copyOf()
code
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中,父类默认是不能强制转换为子类的。
首先最重要有如下几点:
一、Java中数组集合向上转型以后,不能往数组集合中添加引用类型(即父类型)的对象,而应该添加实际类型的对象,好比说``Father[] father = son[],你就不能往
father中添加
Father类型了,而应该是
Son`
二、Java中向上转型是默认容许的,可是向下转型可能会抛出错误,得当心使用!
三、要当心采用Arrays.asList()
建立的集合类型不是java.util.ArrayList
,而是java.util.Arrays$ArrayList
,两个类的不少方法实现方式也不同。
谢谢阅读,欢迎评论区交流!