开发者社区> 问答> 正文

ArrayList中toArray()为什么不支持强制类型转换?[Java]报错

"

问题描述

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

" ![image.png](https://ucc.alicdn.com/pic/developer-ecology/986eeb03314544929eb27d396cf703ba.png)

展开
收起
因为相信,所以看见。 2020-05-25 15:58:23 1140 0
1 条回答
写回答
取消 提交回答
  • 阿里,我所有的向往

    "

    (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[]

    ######

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

    ######

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

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

    ######

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

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

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

    ######

    提示:关键还是要看清楚源码
    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泛型的原理

    " ![image.png](https://ucc.alicdn.com/pic/developer-ecology/388561be30914660a2cbd3d1d239600e.png)
    2020-05-26 09:48:39
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
Spring Cloud Alibaba - 重新定义 Java Cloud-Native 立即下载
The Reactive Cloud Native Arch 立即下载
JAVA开发手册1.5.0 立即下载