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
。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
(T[]) Arrays.copyOf(elementData, size, a.getClass());
强转可以成功是因为数组的类型本身就为a.getClass()
。通过
Array.newInstance(newType.getComponentType(), newLength)
创建的(目标类型)数组对象。String[] y = (String[]) x.toArray()
无法强制转换是因为array
的数据类型为Object[]
而不是String[]
。String
是Object
的子类,但是String[]
不是Object[]
的子类,所以对于实际类型是String
的Object
引用是可以强转成String
的。但是Object[]
怎么都不能强转成String[]
,只能采用个个赋值的方式,把里面的引用挨个强转再拷贝过来(当然可以用Arrays.copyOf()
来做这个事情。把子類的值轉回子類才是合法的
把Object[]的值cast成String[], 這個行為和
(Integer)(new Object())
同類ArrayList的泛型
E
不是必选的,当未指定泛型的时候,例如:这种情况第二种方法是不适用的,也没办法做强制类型转换
提示:关键还是要看清楚源码
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)
这个三目运算意思是说,如果你给我的那个数组,是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泛型的原理