素材牛VIP会员
ArrayList中toArray()为什么不支持强制类型转换?[Java]
 风***扬  分类:Java代码  人气:1097  回帖:5  发布于6年前 收藏

问题描述

ArrayList提供了两个toArray()函数:

Object[] toArray()
<T> T[] toArray(T[] a)

通常如果将ArrayList转换成array通常都是都是使用第二种方式,因为第一种方式如果进行强制类型转换会造成java.lang.ClassCastException,因为它返回的是Object[]但是Java在合法的情况下是支持父类转子类的,为什么会出现这种情况?

比如下面这样明显是合法的:

Object x = new Integer(0);
System.out.println((Integer) x);

p.s: 对于上面问题我知道正确的做法是下面这样:

String[] y = x.toArray(new String[0]);

只是不知道为什么不支持强制类型转换?

String[] y = (String[]) x.toArray()

报错信息

List<String> x = new ArrayList<String>();
x.add("sherlock");
String[] y = (String[]) x.toArray();
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
...

参考资料

get源代码

参考在ArrayList中get的源代码,elementData是一个Object数组,却可以强制类型转换成E:

111     private transient Object[] elementData;

336     @SuppressWarnings("unchecked")
337     E elementData(int index) {
338         return (E) elementData[index];
339     }

348     public E get(int index) {
349         rangeCheck(index);
350 
351         return elementData(index);
352     }

toArray()源代码

295     public Object[] toArray() {
296         return Arrays.copyOf(elementData, size);
297     }

toArray(T[] a)源代码

(T[]) Arrays.copyOf(elementData, size, a.getClass());也是一个强制类型转换。

323     @SuppressWarnings("unchecked")
324     public <T> T[] toArray(T[] a) {
325         if (a.length < size)
326             // Make a new array of a's runtime type, but my contents:
327             return (T[]) Arrays.copyOf(elementData, size, a.getClass());
328         System.arraycopy(elementData, 0, a, 0, size);
329         if (a.length > size)
330             a[size] = null;
331         return a;
332     }

ClassCastException

Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance. For example, the following code generates a ClassCastException:

Object x = new Integer(0);
System.out.println((String) x);

答案

参考@kevinz :

2745    public static <T> T[] copyOf(T[] original, int newLength) {
2746        return (T[]) copyOf(original, newLength, original.getClass());
2747    }
2771    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
2772        T[] copy = ((Object)newType == (Object)Object[].class)
2773            ? (T[]) new Object[newLength]
2774            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
2775        System.arraycopy(original, 0, copy, 0,
2776                         Math.min(original.length, newLength));
2777        return copy;
2778    }

Object[] toArray()对应的是(T[]) new Object[newLength],这里T就是Object

<T> T[] toArray(T[] a)对应的是(T[]) Array.newInstance(newType.getComponentType(), newLength),这里T就是String

 标签:泛型java

讨论这个帖子(5)垃圾回帖将一律封号处理……

Lv1 新人
in***ex 职业无 6年前#1

子類的值轉回子類才是合法的

把Object[]的值cast成String[], 這個行為和(Integer)(new Object())同類

Lv1 新人
凌***志 移动开发工程师 6年前#2

提示:关键还是要看清楚源码
List<Integer> list = new ArrayList<Integer>();
list.add(1);
1.先分析list.toArray();
Integer[] newArray = (Integer[])list.toArray();//会报错
list.toArray(new Integer[1]);//不会报错
这个方法其实调用的是下面这个方法
Arrays.copyOf(elementData, size); 而这里的elementDate是什么?ArrayList,大家应该知道ArrayList底层是数组实现的。
那么问题来了,这个底层的数组是什么,里面的元素是什么类型?看一眼源码就知道,这个数组是Object[],为啥是这个啊,不是Integer[]?
因为泛型会擦除啊,所以底层的数组里面的元素是Object类型,而不是Integer,只有当取出元素的时候才会强转成Integer,具体了解泛型去吧。
接着Arrays.copyOf(elementData, size);这个方法会调用如下方法
Arrays.copyOf(original, newLength, original.getClass());
最重要的一点来了,这个方法里面有个三目运算
T[] copy = ((Object)newType == (Object)Object[].class)

    ? (T[]) new Object[newLength]
    : (T[]) Array.newInstance(newType.getComponentType(), newLength);

这个三目运算意思是说,如果你给我的那个数组,是Object[]的,那么我的T[]就是Object[]的,实际就是T[] copy = new Object[1];
如果你给我的不是Objcet[].class的,那么我就newInstance一个实际类型的T[],
比如上面的list.toArray(new Integer[1]);实际就是:T[] copy = new Integer[1];
答案已经有了啊,很明显的:
T[] copy = new Object[1]; copy不能强制转换为 Integer[];
为什么?因为如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的。
T[] copy = new Integer[1]; copy能强制转换为 Integer[];
为什么?因为如果父类引用的对象是指向子类的对象,那么在向下转型的过程中是安全的。
不理解的兄弟们可以去看看java泛型的原理

Lv2 入门
he***ba JS工程师 6年前#3

(T[]) Arrays.copyOf(elementData, size, a.getClass()); 强转可以成功是因为数组的类型本身就为a.getClass()

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;
}

通过Array.newInstance(newType.getComponentType(), newLength)创建的(目标类型)数组对象。


String[] y = (String[]) x.toArray()无法强制转换是因为array的数据类型为Object[]而不是String[]

Lv1 新人
多***悟 学生 6年前#4

ArrayList的泛型E不是必选的,当未指定泛型的时候,例如:

ArrayList list = new ArrayList();
list.add("test");
list.add(123);
list.add(11.22);

这种情况第二种方法是不适用的,也没办法做强制类型转换

Lv4 码徒
go***le 学生 6年前#5

StringObject 的子类,但是 String[] 不是 Object[] 的子类,所以对于实际类型是 StringObject 引用是可以强转成 String 的。但是 Object[] 怎么都不能强转成 String[],只能采用个个赋值的方式,把里面的引用挨个强转再拷贝过来(当然可以用 Arrays.copyOf() 来做这个事情。

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取