如何在 Java 中连接两个数组?

我需要在 Java 中串联两个String数组。

void f(String[] first, String[] second) {
    String[] both = ???
}

最简单的方法是什么?

答案

我从良好的旧 Apache Commons Lang 库中找到了单行解决方案。
ArrayUtils.addAll(T[], T...)

码:

String[] both = ArrayUtils.addAll(first, second);

这是一个简单的方法,它将两个数组连接起来并返回结果:

public <T> T[] concatenate(T[] a, T[] b) {
    int aLen = a.length;
    int bLen = b.length;

    @SuppressWarnings("unchecked")
    T[] c = (T[]) Array.newInstance(a.getClass().getComponentType(), aLen + bLen);
    System.arraycopy(a, 0, c, 0, aLen);
    System.arraycopy(b, 0, c, aLen, bLen);

    return c;
}

请注意,它不适用于原始数据类型,仅适用于对象类型。

以下稍微复杂一些的版本适用于对象数组和基本数组。它通过使用T代替T[]作为参数类型来实现。

通过选择最通用的类型作为结果的组件类型,还可以连接两种不同类型的数组。

public static <T> T concatenate(T a, T b) {
    if (!a.getClass().isArray() || !b.getClass().isArray()) {
        throw new IllegalArgumentException();
    }

    Class<?> resCompType;
    Class<?> aCompType = a.getClass().getComponentType();
    Class<?> bCompType = b.getClass().getComponentType();

    if (aCompType.isAssignableFrom(bCompType)) {
        resCompType = aCompType;
    } else if (bCompType.isAssignableFrom(aCompType)) {
        resCompType = bCompType;
    } else {
        throw new IllegalArgumentException();
    }

    int aLen = Array.getLength(a);
    int bLen = Array.getLength(b);

    @SuppressWarnings("unchecked")
    T result = (T) Array.newInstance(resCompType, aLen + bLen);
    System.arraycopy(a, 0, result, 0, aLen);
    System.arraycopy(b, 0, result, aLen, bLen);        

    return result;
}

这是一个例子:

Assert.assertArrayEquals(new int[] { 1, 2, 3 }, concatenate(new int[] { 1, 2 }, new int[] { 3 }));
Assert.assertArrayEquals(new Number[] { 1, 2, 3f }, concatenate(new Integer[] { 1, 2 }, new Number[] { 3f }));

可以编写一个完全通用的版本,甚至可以扩展为连接任意数量的数组。此版本需要 Java 6,因为它们使用Arrays.copyOf()

两种版本都避免创建任何中间List对象,并使用System.arraycopy()来确保尽可能快地复制大型阵列。

对于两个数组,它看起来像这样:

public static <T> T[] concat(T[] first, T[] second) {
  T[] result = Arrays.copyOf(first, first.length + second.length);
  System.arraycopy(second, 0, result, first.length, second.length);
  return result;
}

对于任意数量的数组(> = 1),它看起来像这样:

public static <T> T[] concatAll(T[] first, T[]... rest) {
  int totalLength = first.length;
  for (T[] array : rest) {
    totalLength += array.length;
  }
  T[] result = Arrays.copyOf(first, totalLength);
  int offset = first.length;
  for (T[] array : rest) {
    System.arraycopy(array, 0, result, offset, array.length);
    offset += array.length;
  }
  return result;
}

在 Java 8 中使用Stream

String[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b))
                      .toArray(String[]::new);

或者像这样,使用flatMap

String[] both = Stream.of(a, b).flatMap(Stream::of)
                      .toArray(String[]::new);

为此,您必须使用反射:

@SuppressWarnings("unchecked")
T[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b)).toArray(
    size -> (T[]) Array.newInstance(a.getClass().getComponentType(), size));

或与心爱的番石榴

String[] both = ObjectArrays.concat(first, second, String.class);

此外,还有原始数组的版本:

  • Booleans.concat(first, second)
  • Bytes.concat(first, second)
  • Chars.concat(first, second)
  • Doubles.concat(first, second)
  • Shorts.concat(first, second)
  • Ints.concat(first, second)
  • Longs.concat(first, second)
  • Floats.concat(first, second)

使用 Java API:

String[] f(String[] first, String[] second) {
    List<String> both = new ArrayList<String>(first.length + second.length);
    Collections.addAll(both, first);
    Collections.addAll(both, second);
    return both.toArray(new String[both.size()]);
}

您可以在两行代码中附加两个数组。

String[] both = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, both, first.length, second.length);

这是一种快速有效的解决方案,将适用于原始类型以及所涉及的两种方法都已重载。

您应避免使用涉及 ArrayList,流等的解决方案,因为这些解决方案将需要分配临时内存而无用。

您应该避免大型阵列的for循环,因为这些循环效率不高。内置方法使用极快的块复制功能。

一个100%旧的 Java解决方案, 没有 System.arraycopy (例如,在 GWT 客户端中不可用):

static String[] concat(String[]... arrays) {
    int length = 0;
    for (String[] array : arrays) {
        length += array.length;
    }
    String[] result = new String[length];
    int pos = 0;
    for (String[] array : arrays) {
        for (String element : array) {
            result[pos] = element;
            pos++;
        }
    }
    return result;
}

我最近与过多的内存轮换问题进行了斗争。如果已知 a 和 / 或 b 通常为空,则这是 silvertab 代码的另一种改编(也已生成):

private static <T> T[] concatOrReturnSame(T[] a, T[] b) {
    final int alen = a.length;
    final int blen = b.length;
    if (alen == 0) {
        return b;
    }
    if (blen == 0) {
        return a;
    }
    final T[] result = (T[]) java.lang.reflect.Array.
            newInstance(a.getClass().getComponentType(), alen + blen);
    System.arraycopy(a, 0, result, 0, alen);
    System.arraycopy(b, 0, result, alen, blen);
    return result;
}

编辑:这篇文章的先前版本指出,这样的数组重用应明确记录在案。正如 Maarten 在评论中指出的那样,通常最好删除 if 语句,这样就不需要文档。但是话又说回来,那些 if 语句首先是这种特定优化的重点。我会在这里留下这个答案,但要小心!

Functional Java库具有一个数组包装器类,该类为数组配备了诸如连接之类的便捷方法。

import static fj.data.Array.array;

... 接着

Array<String> both = array(first).append(array(second));

要取出未包装的阵列,请致电

String[] s = both.array();