一、前言
在分析ArrayList源码是,看到toArray()有这么一句:c.toArray might (incorrectly) not return Object[] (see 6260652)。网上百度一下,原来,这是一个官方bug。经过本人分析,说的就是public Object[] toArray() 返回的类型不一定就是 Object[],其类型取决于其返回的实际类型.
二、bug 分析
ArrayList 中 toArray() 的源码如下:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
该方法为什么返回的不一定就是 Object[] 呢?原因很简单,因为由于继承的原因,我们父类实例的具体类型,实际上是取决于在 new 时,我们所使用的子类类型。
2.1 以代码说明
我们用代码进行说明这个问题。
创建类
首先创建2个类。
public static class Father {
}
public static class Son extends Father {
}
测试案例1
我们先进行第一次测试,测试代码如下:
/**
* 测试 java.lang.ArrayStoreException
* - 父类(抽象类、接口)对象的实际类型,取决于其实例化时子类的类型
*/
@org.junit.Test
public void test1 () {
Son[] sons = new Son[]{new Son(), new Son()};
System.out.println(sons.getClass()); // class [Lcom.johnnie.test.Test$Son;
Father[] fathers = sons;
System.out.println(fathers.getClass()); // class [Lcom.johnnie.test.Test$Son;
// fathers[0] = new Father(); // java.lang.ArrayStoreException
}
对于 fathers[0] = new Father(); 这句话,为什么会报错?报错原因是因为 fathers 实际类型是 Son[],由于数组中元素类型都是son类型的,因而会出现向下转型异常。也就是说假如我们有 1 个Object[]数组,并不代表着我们可以将Object对象存进去,这取决于数组中元素实际的类型
测试案例2
我们现在进行第二次测试,这次测试就会真正抛出这个官方错误,并且让我们知道为什么会报错。代码如下:
① 新建一个 MyList,该类继承 ArrayList
@SuppressWarnings("serial")
public class MyList<E> extends ArrayList<E> {
// toArray() 的同名方法
public String[] toArray() {
return new String[]{"1", "2", "3"};
}
}
② 测试代码
/**
* 测试:c.toArray might (incorrectly) not return Object[] (see 6260652) 这个官方 bug
*/
@org.junit.Test
public void test3() {
List<String> ss = new LinkedList<String>(); // LinkedList toArray() 返回的本身就是 Object[]
ss.add("123");
Object[] objs = ss.toArray();
objs[0] = new Object();
// 此处说明了:c.toArray might (incorrectly) not return Object[] (see 6260652)
ss = new MyList<String>();
objs = ss.toArray();
System.out.println(objs.getClass()); // class [Ljava.lang.String;
objs[0] = new Object(); // java.lang.ArrayStoreException: java.lang.Object
}
通过控制打印,我们知道,当我们调用 List的 toArray() 时,我们实际上调用的是其具体实现类 MyList 中重写的 String[] toArray() 方法。因此,返回数组并不是 Object[] 而是 String[],而向下转型是不安全的,因此会抛出异常。
通过分析这个官方bug,我们就记住了:
1. 抽象类(接口)其具体类型,取决于实例化时所采用的导出类的类型。
2. 子类实现和父类同名的方法,仅仅返回值不一致时,默认调用的是子类的那个实现方法。如 MyList 中的 String[] toArray()
前往 bascker/javaworld 获取更多 Java 知识