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);
}

这里有两个问题:

  1. 额外空间开销:需要临时数组存储(虽然不影响时间复杂度)
  2. 冗余循环: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转为double[]:

@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获取。


原始标题:Converting Float ArrayList to Primitive Array in Java | Baeldung