Java 集合转数组(附 3 种方法),新手避坑一次讲清

编程狮(w3cschool.cn) 2026-09-28 10:41:19 浏览数 (24)
反馈

Java 里 List 转数组看着只有一行,实际踩坑不少。本文用最小可运行的 Java 示例,讲清 toArray()、toArray(T[]) 和 Stream 三种写法的差别,重点说明泛型擦除后为什么 toArray() 拿回来的是 Object[]、什么时候会多出一个 null、以及如何避免。核心结论是:想要什么类型的数组,就用带参数类型的 toArray(T[]) 写法,这是唯一能同时保证类型正确和长度精确的方式。下面每段代码都给出逐行说明和常见失败。先确认 JDK 版本,再决定用哪种写法。

Java 集合转数组三种方法对比示意图

一、先看结论:三种写法怎么选

下面这张表给出三种写法的适用场景和注意点。选择的标准是「你要什么类型的数组」,不是「哪种代码更短」。

写法 返回类型 注意点
toArray() Object[] 泛型擦除所致,需逐个转型,容易 ClassCastException
toArray(new T[0]) 指定类型的数组 推荐写法,类型与长度都正确,JDK 11+ 更推荐 new T[0]
Stream toArray() 需给生成器 写法更灵活,适合中间还有 map/filter 的场景

三种写法返回类型与风险对照图

需要提醒的是,toArray() 无参写法几乎总是错的选择:它返回的数组元素类型由运行时决定,转型失败时报错发生在取值那一句,而不是转换那一行,排错时容易被误导。想在线对比不同写法,可用 在线代码实例 验证实际结果。

二、环境确认与最小示例

先确认 JDK 版本,再跑最小示例。示例基于 JDK 8 及以上,接口行为一致;用 JDK 11 时 toArray(T[]) 会进一步省去反射开销。Java 版本差异对下面的结论没有影响。

# 查看 JDK 版本,建议 8 及以上
java -version

下面是一份最小示例,把 List<String> 转成字符串数组。这一份就是推荐写法,后面三种写法都围绕它展开对比。

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // 准备一个字符串集合,作为转换的输入
        List<String> list = Arrays.asList("apple", "banana", "cherry");

        // 推荐写法:传入长度为 0 的数组,让 JVM 按集合大小创建正确类型
        String[] arr = list.toArray(new String[0]);

        // 逐个打印,验证转换结果正确
        for (String s : arr) {
            System.out.println(s);
        }
    }
}

这段代码的判定标准是:编译无警告,运行输出三个水果名。多数时候你需要的就是这一份写法,剩下三种只在特殊场景下才值得换。想系统补齐 Java 基础,可以看 Java 教程。集合转数组看起来只有一行,真正决定代码能不能长期维护的,是当初选的写法在泛型擦除之后还剩多少信息,这一点值得在动手前想清楚。

三、方法一:toArray() 无参写法

无参写法返回 Object[],这是泛型擦除的直接结果:编译期看到的 List<String> 在运行期只剩 List,toArray() 只能给出 Object[]。

// 写法一:无参 toArray(),返回 Object[]
Object[] objs = list.toArray();

// 取第一个元素时必须手动转型
String first = (String) objs[0];  // 运行时转型,类型对得上才能过
System.out.println(first);

这里的关键点是:报错位置会骗人。如果集合里混入非字符串元素,异常发生在转型那一行,而不是转换那一行。修复方向是改用 toArray(new String[0]),从源头保证元素类型。另一个常见失败是循环里直接遍历 Object[] 却按 String 使用,也会抛类型转换异常。

四、方法二:toArray(new T[0]) 指定类型

带参数写法传入一个数组实例,JDK 据此创建正确类型的数组。传入长度 0 的空数组最省事,运行时会按实际集合大小分配。改完之后用项目对应的 JDK 版本重新编译一遍,预期仍能编译通过、运行输出与改动前一致;若预分配长度的场景出现空值,先检查预分配的长度是否小于集合实际大小,这一步能挡住绝大多数数组长度相关的失败。

// 写法二:传入空数组,由运行时按集合大小创建 String[]
String[] arr = list.toArray(new String[0]);

// 也可预分配固定长度,结果一致,只是多了一次拷贝
String[] arr2 = list.toArray(new String[list.size()]);

两种方式结果相同,差别在于内部是否多一次数组分配。预分配写法在集合很大时略省一次拷贝,但代码更啰嗦。这里还有一处性能上的差别值得提一句:预分配的写法在集合很大时会省掉一次数组分配和拷贝判断,但绝大多数业务场景下这点开销可以忽略,代码的可读性反而更值钱。另外要提醒一个边界:传入的数组比集合小时会重新分配,比集合大时超出的位置会被填 null。,比集合大时超出的位置会被填 null,所以固定长度只在确实没问题时才稳。这个细节在下游按数组长度遍历时最容易出问题。

五、方法三:Stream 写法

如果转换前还需要过滤或映射,Stream 写法能少写几行。终态 toArray 需要给一个数组构造器引用。

import java.util.stream.Collectors;

// 写法三:过滤后转换,中间操作与转换一步完成
String[] arr = list.stream()
        .filter(s -> s.length() > 5)   // 只保留长度大于 5 的元素
        .toArray(String[]::new);       // 给数组生成器,指定元素类型

for (String s : arr) {
    System.out.println(s);
}

String[]::new 就是数组构造器引用,等价于 new String[0]。判定标准是中间操作后是否还需要复用流;不需要就直接链式转换。失败点同样在类型:Stream 的 toArray() 若不传生成器,返回的仍是 Object[]。

现象 常见原因 修复方向
报 ClassCastException 用了无参 toArray() 后直接当 String[] 用 改用 toArray(new String[0])
数组末尾多一个 null 传入的数组长度大于集合长度 用空数组 new T[0],让运行时按实际大小分配
预分配长度后少元素 传入长度小于集合长度时触发重新分配 确认长度,或统一用 new T[0]
遍历时报类型转换异常 直接遍历 Object[] 却按具体类型使用 转型后使用,或改成带类型的写法

以上失败与修复均来自真实边界场景,未在本机执行,按 Java 官方 API 文档推断。

总结

Java 集合转数组,最稳的一句是:想要什么类型的数组,就用 toArray(new T[0])。无参 toArray() 因为泛型擦除只能返回 Object[],取值时要手动转型,失败发生在转型那一行而不是转换那一行;预分配固定长度时,数组过大还会在末尾补 null。只有在转换前还需要过滤或映射时,才值得换成 Stream 写法,并且别忘了给 toArray(String[]::new) 数组生成器。按这个顺序选,绝大多数转换代码都能一次写对。

延伸学习

想在编程狮系统学 Java 集合,可以顺着下面三篇深入:

常见问题

Q:为什么 toArray() 返回的是 Object[],不是 String[]?

A:因为泛型擦除。编译期看到的 List<String> 到运行期只剩 List,JVM 无法知道元素类型,只能给出 Object[]。要拿回具体类型,就得用带参数类型的 toArray(new String[0])。

Q:new String[0] 和 new String[list.size()] 该用哪个?

A:优先 new String[0]。两者结果相同,前者让运行时按集合大小分配,代码更简洁;后者预分配会多一次拷贝判断。预分配只在集合很大且已确认长度合适的场景下才略优。

Q:数组里为什么会出现 null?

A:传入的数组长度大于集合长度时,超出的位置会被填成 null。比如集合 3 个元素却传了 new String[5],下标 3、4 就是 null。下游按长度遍历时会读到一个 null,进而触发空指针。

Q:转换前要过滤,用哪种写法?

A:用 Stream。把 filter、map 写在中间,终态用 toArray(String[]::new) 指定类型。注意 Stream 的 toArray() 若不传生成器,返回的同样是 Object[],类型问题不会自动变好。

0 人点赞