Java中Collection.toArray()无参返回Object[],不可强转为具体类型数组,否则抛ClassCastException;正确做法是传入new String[0],因JVM优化空数组且避免并发导致size不准。

Java中Collection.toArray()不加参数会返回Object[],不能直接强转为具体类型数组
这是最常踩的坑:写list.toArray()后试图(String[])list.toArray(),运行时抛ClassCastException。因为无参toArray()返回的是Object[],而Object[]和String[]是不同运行时类型,JVM禁止这种向下强转。
根本原因是Java数组是协变的,但泛型是擦除的——编译器看到List<string></string>,运行时只剩List,toArray()无法知道你要什么类型。
- 正确做法是传入一个带类型的数组作为参数,例如
list.toArray(new String[0]) - 传
new String[0]比new String[list.size()]更优:现代JVM对空数组优化好,且避免因并发修改导致size不准 - 如果传入数组长度 ≥ 集合大小,多余位置填
null;若不足,会自动创建新数组(所以传[0]完全安全)
为什么toArray(T[])要传new T[0]而不是new T[list.size()]
表面上看,预分配准确长度似乎更“高效”,但实际在HotSpot里,new T[0]的性能更好,且语义更清晰:它只是告诉方法“我要T类型的数组”,不承担容量管理责任。
传new T[list.size()]的问题在于:
立即学习“Java免费学习笔记(深入)”;
- 集合可能被其他线程修改,
size()调用时刻和toArray()内部遍历时刻不一致,导致数组长度不够,触发扩容逻辑(反而多一次数组复制) - 如果集合为空,
new T[list.size()]变成new T[0],看起来一样,但代码意图模糊 - 某些集合实现(如
CopyOnWriteArrayList)的size()是快照值,但迭代过程仍可能跳过新元素,预分配长度无意义
所以统一用new String[0],既安全又明确表达意图。
toArray()在Stream、Arrays.asList()等场景下的行为差异
不同集合来源对toArray()的影响容易被忽略。比如Arrays.asList("a","b").toArray(new String[0])没问题,但Stream.of("a","b").collect(Collectors.toList()).toArray(new String[0])也没问题——它们都遵守规范。真正危险的是自己写的集合子类或第三方库集合。
- 确保集合实现正确重写了
toArray(T[]):必须检查传入数组长度,够就复用,不够就新建同类型数组 -
Stream.collect(Collectors.toCollection(LinkedList::new))这类构造出的集合,只要底层LinkedList没被篡改,就安全 - 如果遇到
UnsupportedOperationException,说明该集合未实现泛型toArray,只能退回到Object[]再逐个转型(不推荐,易错)
强转失败时的替代方案:用Stream.toArray()或手动循环
当确定集合非空且类型稳定,又不想依赖toArray(T[])实现质量时,可绕过它:
- 用
list.stream().map(e -> (String)e).toArray(String[]::new)——利用Stream的类型推导,但有额外开销 - 手动循环创建:
String[] arr = new String[list.size()]; for (int i = 0; i ,适合极小集合或需要校验的场景 - 注意:不要用
list.toArray()[i]再强转单个元素,这仍是Object[],每次取都要(String),且失去数组类型信息
真正关键的不是“怎么转”,而是始终意识到:Java里没有真正的泛型数组,所有类型安全都靠你传对那个new T[0]——少一个方括号,就可能在线上崩一次。


















