1. 概述
数据序列是任何项目和编程语言的核心组成部分。在Java中,表示元素序列有两种方式:集合(Collections)和数组(arrays)。本文将重点介绍如何将包装类(wrapper classes)的ArrayList转换为基本类型(primitives)数组。 虽然听起来是个简单任务,但Java API中的一些特性让这个过程变得不那么直观。
2. 简单for循环
最直接的转换方式是使用声明式风格的for循环:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertToPrimitiveArray_thenGetCorrectResult(List<Float> floats) {
float[] actual = new float[floats.size()];
for (int i = 0; i < floats.size(); i++) {
actual[i] = floats.get(i);
}
compareSequences(floats, actual);
}
✅ 优点:代码清晰直观,易于理解
❌ 缺点:对于这种简单任务需要处理太多细节
3. 转换为Float数组
集合API提供了将List转换为数组的方法,但不处理拆箱(unboxing)操作。 不过这个方法仍然值得考虑:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertToWrapperArray_thenGetCorrectResult(List<Float> floats) {
Float[] actual = floats.toArray(new Float[0]);
assertSequences(floats, actual);
}
List类的toArray()方法可以帮助我们完成转换。 但这个API有点让人困惑——我们需要传入一个数组来确保类型正确,结果类型将与传入的数组类型一致。
由于必须传入数组实例,不清楚应该指定多大尺寸,结果数组是否会被截断。实际上完全不用担心尺寸问题,toArray()会在必要时自动扩展数组。
当然也可以直接指定尺寸:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertToWrapperArrayWithPreSizedArray_thenGetCorrectResult(List<Float> floats) {
Float[] actual = floats.toArray(new Float[floats.size()]);
assertSequences(floats, actual);
}
⚠️ 踩坑提示:虽然看起来像优化,但实际上并非如此。Java编译器能完美处理尺寸问题。而且在多线程环境中调用size()可能导致问题,因此推荐使用空数组方式。
4. 拆箱数组
虽然数值和布尔值有拆箱概念,但尝试直接拆箱数组会导致编译时错误。因此需要逐个元素拆箱:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenUnboxToPrimitiveArray_thenGetCorrectResult(List<Float> floats) {
float[] actual = new float[floats.size()];
Float[] floatArray = floats.toArray(new Float[0]);
for (int i = 0; i < floats.size(); i++) {
actual[i] = floatArray[i];
}
assertSequences(floats, actual);
}
这里有两个问题:
- 额外空间开销:需要临时数组存储(虽然不影响时间复杂度)
- 冗余循环:for循环只做了隐式拆箱,可以优化掉
使用Apache Commons的工具类可以简化:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertToPrimitiveArrayWithArrayUtils_thenGetCorrectResult(List<Float> floats) {
float[] actual = ArrayUtils.toPrimitive(floats.toArray(new Float[]{}));
assertSequences(floats, actual);
}
这样得到简洁的单行解决方案。toPrimitive()方法封装了之前的逻辑并添加了额外检查:
public static float[] toPrimitive(final Float[] array) {
if (array == null) {
return null;
}
if (array.length == 0) {
return EMPTY_FLOAT_ARRAY;
}
final float[] result = new float[array.length];
for (int i = 0; i < array.length; i++) {
result[i] = array[i].floatValue();
}
return result;
}
✅ 优点:代码简洁优雅
❌ 缺点:需要引入额外库(或自己实现类似方法)
5. 使用Stream
处理集合时,可以用Stream复制循环中的逻辑。 Stream API能同时完成List转换和值拆箱。但有个坑:Java没有FloatStream。
如果不介意浮点数类型差异,可以用DoubleStream将ArrayList
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertingToPrimitiveArrayUsingStreams_thenGetCorrectResult(List<Float> floats) {
double[] actual = floats.stream().mapToDouble(Float::doubleValue).toArray();
assertSequences(floats, actual);
}
⚠️ 注意:虽然成功转换了List,但浮点数表示发生了变化(float→double)。因为Java只提供IntStream、LongStream和DoubleStream。
6. 自定义收集器
也可以实现自定义Collector封装所有逻辑:
public class FloatCollector implements Collector<Float, float[], float[]> {
private final int size;
private int index = 0;
public FloatCollector(int size) {
this.size = size;
}
@Override
public Supplier<float[]> supplier() {
return () -> new float[size];
}
@Override
public BiConsumer<float[], Float> accumulator() {
return (array, number) -> {
array[index] = number;
index++;
};
}
// 其他非关键方法
}
其他非关键方法包括必要的存根方法和空操作的终结器:
public class FloatCollector implements Collector<Float, float[], float[]> {
// 关键方法已省略
@Override
public BinaryOperator<float[]> combiner() {
return null;
}
@Override
public Function<float[], float[]> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
现在展示这个有点"取巧"的收集器:
@ParameterizedTest
@MethodSource("floatListProvider")
void givenListOfWrapperFloat_whenConvertingWithCollector_thenGetCorrectResult(List<Float> floats) {
float[] actual = floats.stream().collect(new FloatCollector(floats.size()));
assertSequences(floats, actual);
}
❌ 缺点:虽然玩转Stream API很有趣,但这个方案过于复杂,且在此场景下没有任何优势。另外,这个收集器在多线程环境中可能有问题,需要考虑线程安全。
7. 总结
处理数组和集合是日常开发中的常见操作。虽然List提供了更好的接口,但有时仍需转换为简单数组。转换过程中的额外拆箱操作增加了复杂度,但通过一些技巧、自定义方法或第三方库可以简化流程。
本文所有代码示例可在GitHub获取。