在Java中, generic type exists at compile-time only . 在运行时,有关泛型类型的信息(如您的情况 <String> )将被删除并替换为 Object 类型(请查看type erasure) . 这就是为什么在运行时 toArray() 不知道用于创建新数组的精确类型,因此它使用 Object 作为最安全的类型,因为每个类都扩展了Object,因此它可以安全地存储任何类的实例 .
现在的问题是你无法将Object []的实例转换为String [] .
为什么?看看这个例子(假设 class B extends A ):
//B extends A
A a = new A();
B b = (B)a;
虽然这样的代码会编译,但在运行时我们会看到抛出 ClassCastException 因为引用 a 所持有的实例实际上并不是类型 B (或其子类型) . 为什么会出现这个问题(为什么需要抛出此异常)?其中一个原因是 B 可能有新的方法/字段 A 没有,所以有人可能会尝试通过 b 引用使用这些新成员,即使它们没有持有实例也支持它们 . 换句话说,我们最终可能会尝试使用不存在的数据,这可能会导致许多问题 . 因此,为了防止这种情况,JVM抛出异常,并停止进一步的潜在危险代码 .
你现在可以问"So why aren't we stopped even earlier? Why code involving such casting is even compilable? Shouldn't compiler stop it?" . 答案是:不,因为编译器无法确定 a 引用所持有的实例的实际类型是什么,并且它有可能保存类 B 的实例,它将支持 b 引用的接口 . 看看这个例子:
A a = new B();
// ^------ Here reference "a" holds instance of type B
B b = (B)a; // so now casting is safe, now JVM is sure that `b` reference can
// safely access all members of B class
Object[] arr = new String[] { "ab", "cd" }; //OK - because
// ^^^^^^^^ `arr` holds array of subtype of Object (String)
String[] arr2 = (String[]) arr; //OK - `arr2` reference will hold same array of same type as
// reference
//B extends A
List<A> elements = new ArrayList<A>();
elements.add(new B());
elements.add(new B());
现在最常见的类型是 B ,而不是 A 所以 toArray()
A[] arr = elements.toArray();
将返回 B class new B[] 的数组 . 这个数组的问题是,虽然编译器允许你通过添加 new A() 元素来编辑它的内容,但是你会得到 ArrayStoreException 因为 B[] 数组只能包含类 B 或其子类的元素,以确保所有元素都支持接口 B ,但 A 的实例可能没有 B 的所有方法/字段 . 所以这个解决方案并不完美 .
6 回答
正确的方法是:
我想在这里添加其他很好的答案并解释你如何使用Javadocs来回答你的问题 .
toArray()
(无参数)的Javadoc是here . 如您所见,此方法返回Object[]
而不是String[]
,它是列表的运行时类型的数组:但是,在该方法的正下方,the Javadoc为
toArray(T[] a)
. 正如你所看到的,这个方法返回一个T[]
,其中T
是你传入的数组的类型 . 首先,这似乎是你不清楚为什么你传入一个数组(你是否添加它,使用它)仅适用于类型等) . 该文档清楚地表明,传递数组的目的主要是定义要返回的数组类型(这正是您的用例):当然,需要理解泛型(如其他答案中所述)才能真正理解这两种方法之间的区别 . 然而,如果你第一次去Javadocs,你通常会找到你的答案,然后亲自看看你还需要学习什么(如果你真的这样做) .
另请注意,在此处阅读Javadoc可帮助您了解传入的数组的结构应该是什么 . 虽然它可能实际上并不重要,但你不应该像这样传入一个空数组:
因为,从doc,此实现检查数组是否足够大以包含集合;如果没有,它会分配一个正确大小和类型的新数组(使用反射) . 当你可以轻松传入大小时,不需要额外的开销来创建一个新数组 .
通常情况下,Javadocs为您提供了丰富的信息和方向 .
嘿等一下,有什么反映?
我可以看到许多答案显示如何解决问题,但只有斯蒂芬的答案是试图解释为什么会出现问题所以我会尝试在这个问题上添加更多内容 . 这是一个关于为什么Object [] toArray没有改为T [] toArray的可能原因的故事,其中泛型软件被引入Java .
为什么String [] stockArr =(String [])stock_list.toArray();不会工作?
在Java中, generic type exists at compile-time only . 在运行时,有关泛型类型的信息(如您的情况
<String>
)将被删除并替换为Object
类型(请查看type erasure) . 这就是为什么在运行时toArray()
不知道用于创建新数组的精确类型,因此它使用Object
作为最安全的类型,因为每个类都扩展了Object,因此它可以安全地存储任何类的实例 .现在的问题是你无法将Object []的实例转换为String [] .
为什么?看看这个例子(假设
class B extends A
):虽然这样的代码会编译,但在运行时我们会看到抛出
ClassCastException
因为引用a
所持有的实例实际上并不是类型B
(或其子类型) . 为什么会出现这个问题(为什么需要抛出此异常)?其中一个原因是B
可能有新的方法/字段A
没有,所以有人可能会尝试通过b
引用使用这些新成员,即使它们没有持有实例也支持它们 . 换句话说,我们最终可能会尝试使用不存在的数据,这可能会导致许多问题 . 因此,为了防止这种情况,JVM抛出异常,并停止进一步的潜在危险代码 .你现在可以问"So why aren't we stopped even earlier? Why code involving such casting is even compilable? Shouldn't compiler stop it?" . 答案是:不,因为编译器无法确定
a
引用所持有的实例的实际类型是什么,并且它有可能保存类B
的实例,它将支持b
引用的接口 . 看看这个例子:现在让我们回到你的阵列 . 如您所见,我们无法将
Object[]
数组的实例转换为更精确的类型String[]
这里的问题有点不同 . 现在我们确定
String[]
数组不会有其他字段或方法,因为每个数组仅支持:[]
运营商,length
提交,方法继承自Object超类型,
所以它不是阵列接口使它变得不可能 . 问题是 Object[] array beside Strings can store any objects (例如
Integers
)所以有可能在一个美好的日子我们最终会尝试调用像strArray[i].substring(1,3)
这样的方法,如Integer
没有这样的方法 .因此,为了确保这种情况永远不会发生,Java数组引用只能保留
与引用类型相同的数组实例(引用
String[] strArr
可以容纳String[]
)子类型数组的实例(
Object[]
可以保存String[]
,因为String
是Object
的子类型),但不能坚持
来自引用的数组类型的超类型数组(
String[]
无法容纳Object[]
)与引用类型无关的类型数组(
Integer[]
无法保存String[]
)换句话说,这样的事情就可以了
您可以说解决此问题的一种方法是在运行时查找所有列表元素之间最常见的类型并创建该类型的数组,但这不适用于列表的所有元素都是派生自通用类型的一种类型的情况 . 看一看
现在最常见的类型是
B
,而不是A
所以toArray()
将返回
B
classnew B[]
的数组 . 这个数组的问题是,虽然编译器允许你通过添加new A()
元素来编辑它的内容,但是你会得到ArrayStoreException
因为B[]
数组只能包含类B
或其子类的元素,以确保所有元素都支持接口B
,但A
的实例可能没有B
的所有方法/字段 . 所以这个解决方案并不完美 .这个问题的最佳解决方案是通过将此类型作为方法参数传递来明确告诉应该返回什么类型的数组
toArray()
要么
发生的事情是
stock_list.toArray()
正在创建Object[]
而不是String[]
,因此类型转换失败1 .正确的代码是:
甚至
有关更多详细信息,请参阅
List.toArray
的两个重载的javadoc .后一版本使用零长度数组来确定结果数组的类型 . (令人惊讶的是,执行此操作要比预先分配...更快,对于最近的Java版本 . 有关详细信息,请参阅https://stackoverflow.com/a/4042464/139985 . )
从技术角度来看,此API行为/设计的原因是
List<T>.toArray()
方法的实现没有<T>
在运行时的信息 . 它只知道原始元素类型是Object
. 相反,在另一种情况下,数组参数给出了数组的基本类型 . (如果提供的数组大到足以容纳列表元素,则使用它 . 否则,将分配相同类型和更大大小的新数组并作为结果返回 . )1 - 在Java中,Object []与String []的赋值不兼容 . 如果是,那么你可以这样做:
这显然是无稽之谈,这就是为什么数组类型通常不是赋值兼容的原因 .
Java 8中的替代方案:
像这样使用 .
试试这个