Concatenate Two Arrays in Java – 在Java中串联两个数组

最后修改: 2021年 4月 30日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to discuss how to concatenate two arrays in Java.

在本教程中,我们将讨论如何在Java中串联两个数组

First, we’ll implement our own methods with the standard Java API.

首先,我们将用标准的Java API实现我们自己的方法。

Then, we’ll have a look at how to solve the problem using commonly used libraries.

然后,我们将看看如何使用常用的库来解决这个问题。

2. Introduction to the Problem

2.对问题的介绍

Quick examples may explain the problem clearly.

快速的例子可以清楚地解释这个问题。

Let’s say, we have two arrays:

比方说,我们有两个数组。

String[] strArray1 = {"element 1", "element 2", "element 3"};
String[] strArray2 = {"element 4", "element 5"};

Now, we want to join them and get a new array:

现在,我们想把它们连接起来,得到一个新的数组。

String[] expectedStringArray = {"element 1", "element 2", "element 3", "element 4", "element 5"}

Also, we don’t want our method only to work with String arrays, so we’ll look for a generic solution.

另外,我们不希望我们的方法只对String数组起作用,所以我们将寻找一个通用的解决方案

Moreover, we shouldn’t forget the primitive array cases. It would be good if our solution works for primitive arrays, too:

此外,我们不应该忘记原始数组的情况。如果我们的解决方案对原始数组也有效,那就更好了。

int[] intArray1 = { 0, 1, 2, 3 };
int[] intArray2 = { 4, 5, 6, 7 };
int[] expectedIntArray = { 0, 1, 2, 3, 4, 5, 6, 7 };

In this tutorial, we’ll address different approaches to solve the problem.

在本教程中,我们将讨论解决这个问题的不同方法。

3. Using Java Collections

3.使用Java Collections

When we look at this problem, a quick solution may come up.

当我们看这个问题时,可能会出现一个快速的解决方案。

Well, Java doesn’t provide a helper method to concatenate arrays. However, since Java 5, the Collections utility class has introduced an addAll(Collection<? super T> c, T… elements) method.

那么,Java并没有提供一个辅助方法来连接数组。然而,从Java 5开始,Collections工具类引入了一个addAll(Collection<? super T> c, T… elements)方法。

We can create a List object, then call this method twice to add the two arrays to the list. Finally, we convert the resulting List back to an array:

我们可以创建一个List对象,然后调用这个方法两次,将两个数组添加到列表中。最后,我们将得到的List转换为一个数组。

static <T> T[] concatWithCollection(T[] array1, T[] array2) {
    List<T> resultList = new ArrayList<>(array1.length + array2.length);
    Collections.addAll(resultList, array1);
    Collections.addAll(resultList, array2);

    @SuppressWarnings("unchecked")
    //the type cast is safe as the array1 has the type T[]
    T[] resultArray = (T[]) Array.newInstance(array1.getClass().getComponentType(), 0);
    return resultList.toArray(resultArray);
}

In the method above, we use Java reflection API to create a generic array instance: resultArray.

在上述方法中,我们使用Java反射API来创建一个通用数组实例。resultArray.

Let’s write a test to verify if our method works:

让我们写一个测试来验证我们的方法是否有效。

@Test
public void givenTwoStringArrays_whenConcatWithList_thenGetExpectedResult() {
    String[] result = ArrayConcatUtil.concatWithCollection(strArray1, strArray2);
    assertThat(result).isEqualTo(expectedStringArray);
}

If we execute the test, it’ll pass.

如果我们执行这个测试,它就会通过。

This approach is pretty straightforward. However, since the method accepts T[] arrays, it doesn’t support concatenating primitive arrays.

这种方法是非常直接的。然而,由于该方法接受T[]数组,它不支持连接原始数组

Apart from that, it’s inefficient as it creates an ArrayList object, and later we call the toArray() method to convert it back to an array. In this procedure, the Java List object adds unnecessary overhead.

除此之外,它的效率很低,因为它创建了一个ArrayList对象,之后我们再调用toArray()方法将其转换为一个数组。在这个过程中,Java的List对象增加了不必要的开销。

Next, let’s see if we can find a more efficient way to solve the problem.

接下来,让我们看看是否能找到一种更有效的方法来解决问题。

4. Using the Array Copy Technique

4.使用阵列复制技术

Java doesn’t offer an array concatenation method, but it provides two array copy methods: System.arraycopy() and Arrays.copyOf().

Java没有提供数组连接的方法,但它提供了两个数组复制的方法。System.arraycopy()Arrays.copyOf()

We can solve the problem using Java’s array copy methods.

我们可以用Java的数组复制方法来解决这个问题。

The idea is, we create a new array, say result, which has result.length = array1.length + array2.length, and copy each array’s elements to the result array.

这个想法是,我们创建一个新的数组,例如result,它的result.length = array1.length + array2.length,并将每个数组的元素复制到result数组。

4.1. Non-Primitive Arrays

4.1.非原则性阵列

First, let’s have a look at the method implementation:

首先,让我们看一下方法的实现。

static <T> T[] concatWithArrayCopy(T[] array1, T[] array2) {
    T[] result = Arrays.copyOf(array1, array1.length + array2.length);
    System.arraycopy(array2, 0, result, array1.length, array2.length);
    return result;
}

The method looks compact. Further, the whole method has created only one new array object: result.

这个方法看起来很紧凑。此外,整个方法只创建了一个新的数组对象。result

Now, let’s write a test method to check if it works as we expect:

现在,让我们写一个测试方法来检查它是否像我们期望的那样工作。

@Test
public void givenTwoStringArrays_whenConcatWithCopy_thenGetExpectedResult() {
    String[] result = ArrayConcatUtil.concatWithArrayCopy(strArray1, strArray2);
    assertThat(result).isEqualTo(expectedStringArray);
}

The test will pass if we give it a run.

如果我们让它运行一下,测试就会通过。

There is no unnecessary object creation. Thus, this method is more performant than the approach using Java Collections.

不存在不必要的对象创建。因此,这种方法比使用Java Collections的方法更具性能。

On the other hand, this generic method only accepts parameters with the T[] type. Therefore, we cannot pass primitive arrays to the method.

另一方面,这个通用方法只接受T[]类型的参数。因此,我们不能向该方法传递原始数组。

However, we can modify the method to make it support primitive arrays.

然而,我们可以修改该方法,使其支持原始数组。

Next, let’s take a closer look at how to add primitive array support.

接下来,让我们仔细看看如何添加原始数组支持。

4.2. Add Primitive Array Support

4.2.增加原始数组支持

To make the method support primitive arrays, we need to change the parameters’ type from T[] to T and do some type-safe checks.

为了使该方法支持原始数组,我们需要将参数的类型从T[]改为T并做一些类型安全检查。

First, let’s take a look at the modified method:

首先,让我们看一下修改后的方法。

static <T> T concatWithCopy2(T array1, T array2) {
    if (!array1.getClass().isArray() || !array2.getClass().isArray()) {
        throw new IllegalArgumentException("Only arrays are accepted.");
    }

    Class<?> compType1 = array1.getClass().getComponentType();
    Class<?> compType2 = array2.getClass().getComponentType();

    if (!compType1.equals(compType2)) {
        throw new IllegalArgumentException("Two arrays have different types.");
    }

    int len1 = Array.getLength(array1);
    int len2 = Array.getLength(array2);

    @SuppressWarnings("unchecked")
    //the cast is safe due to the previous checks
    T result = (T) Array.newInstance(compType1, len1 + len2);

    System.arraycopy(array1, 0, result, 0, len1);
    System.arraycopy(array2, 0, result, len1, len2);

    return result;
}

Obviously, the concatWithCopy2() method is longer than the original version. But it’s not hard to understand. Now, let’s quickly walk through it to understand how it works.

显然,concatWithCopy2()方法比原始版本要长。但它并不难理解。现在,让我们快速浏览一下,了解它是如何工作的。

Since the method now allows parameters with the type T, we need to make sure both parameters are arrays:

由于该方法现在允许类型为T的参数,我们需要确保两个参数都是数组

if (!array1.getClass().isArray() || !array2.getClass().isArray()) {
    throw new IllegalArgumentException("Only arrays are accepted.");
}

It’s still not safe enough if two parameters are arrays. For example, we don’t want to concatenate an Integer[] array and a String[] array. So, we need to make sure the ComponentType of the two arrays are identical:

如果两个参数是数组,这仍然不够安全。例如,我们不想把一个Integer[]数组和一个String[]数组连接起来。因此,我们需要确保两个数组的ComponentType是相同的

if (!compType1.equals(compType2)) {
    throw new IllegalArgumentException("Two arrays have different types.");
}

After the type-safe checks, we can create a generic array instance using the ConponentType object and copy parameter arrays to the result array. It’s quite similar to the previous concatWithCopy() method.

在类型安全检查之后,我们可以使用ConponentType对象创建一个通用数组实例,并将参数数组复制到result数组。这与之前的concatWithCopy()方法很相似。

4.3. Testing the concatWithCopy2() Method

4.3.测试concatWithCopy2()方法

Next, let’s test if our new method works as we expected. First, we pass two non-array objects and see if the method raises the expected exception:

接下来,让我们测试一下我们的新方法是否像我们预期的那样工作。首先,我们传递两个非数组对象,看看该方法是否引发了预期的异常。

@Test
public void givenTwoStrings_whenConcatWithCopy2_thenGetException() {
    String exMsg = "Only arrays are accepted.";
    try {
        ArrayConcatUtil.concatWithCopy2("String Nr. 1", "String Nr. 2");
        fail(String.format("IllegalArgumentException with message:'%s' should be thrown. But it didn't", exMsg));
    } catch (IllegalArgumentException e) {
        assertThat(e).hasMessage(exMsg);
    }
}

In the test above, we pass two String objects to the method. If we execute the test, it passes. This means we’ve got the expected exception.

在上面的测试中,我们向该方法传递了两个String对象。如果我们执行这个测试,它就会通过。这意味着我们已经得到了预期的异常。

Finally, let’s build a test to check if the new method can concatenate primitive arrays:

最后,让我们建立一个测试,检查新方法是否可以连接原始数组。

@Test
public void givenTwoArrays_whenConcatWithCopy2_thenGetExpectedResult() {
    String[] result = ArrayConcatUtil.concatWithCopy2(strArray1, strArray2);
    assertThat(result).isEqualTo(expectedStringArray);

    int[] intResult = ArrayConcatUtil.concatWithCopy2(intArray1, intArray2);
    assertThat(intResult).isEqualTo(expectedIntArray);
}

This time, we called the concatWithCopy2() method twice. First, we pass two String[] arrays. Then, we pass two int[] primitive arrays.

这一次,我们调用了concatWithCopy2()方法两次。首先,我们传递了两个String[]数组。然后,我们传递两个int[]原始数组。

The test will pass if we run it. Now, we can say, the concatWithCopy2() method works as we expected.

如果我们运行它,测试将通过。现在,我们可以说,concatWithCopy2()方法按照我们的预期工作。

5. Using Java Stream API

5.使用Java流API

If the Java version we’re working with is 8 or newer, the Stream API is available. We can also resolve the problem using the Stream API.

如果我们使用的Java版本是8或更新的版本,则可以使用Stream API>。我们也可以用Stream API来解决这个问题。

First, we can get a Stream from an array by the Arrays.stream() method. Also, the Stream class provides a static concat() method to concatenate two Stream objects.

首先,我们可以通过Arrays.stream()方法从一个数组中获得一个Stream。另外,Stream类提供了一个静态的<a href=”https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#concat(java.util.stream.Stream,java.util.stream.Stream)>concat()方法来连接两个Stream对象。

Now, let’s see how to concatenate two arrays with Stream.

现在,让我们看看如何用Stream.串联两个数组。

5.1. Concatenating Non-Primitive Arrays

5.1.串联非原生数组

Building a generic solution using Java Streams is pretty simple:

使用Java Streams构建一个通用解决方案是非常简单的。

static <T> T[] concatWithStream(T[] array1, T[] array2) {
    return Stream.concat(Arrays.stream(array1), Arrays.stream(array2))
      .toArray(size -> (T[]) Array.newInstance(array1.getClass().getComponentType(), size));
}

First, we convert two input arrays to Stream objects. Second, we concatenate the two Stream objects using the Stream.concat() method.

首先,我们将两个输入数组转换为Stream对象。其次,我们使用Stream.concat()方法将这两个Stream对象连接起来。

Finally, we return an array containing all elements in the concatenated Stream.

最后,我们返回一个包含串联的Stream.中所有元素的数组。

Next, let’s build a simple test method to check if the solution works:

接下来,让我们建立一个简单的测试方法来检查该解决方案是否有效。

@Test
public void givenTwoStringArrays_whenConcatWithStream_thenGetExpectedResult() {
    String[] result = ArrayConcatUtil.concatWithStream(strArray1, strArray2);
    assertThat(result).isEqualTo(expectedStringArray);
}

The test will pass if we pass two String[] arrays.

如果我们传递两个String[]数组,测试将通过。

Probably, we’ve noticed that our generic method accepts parameters in the T[] type. Therefore, it won’t work for primitive arrays.

可能我们已经注意到,我们的泛型方法接受的参数是T[]类型。因此,它对原始数组不起作用

Next, let’s see how to concatenate two primitive arrays using Java Streams.

接下来,让我们看看如何使用Java Streams将两个原始数组连接起来。

5.2. Concatenating Primitive Arrays

5.2.连接原始数组

The Stream API ships different Stream classes that can convert the Stream object to the corresponding primitive array, such as IntStream, LongStream, and DoubleStream.

Stream API提供了不同的Stream类,可以将Stream对象转换为相应的原始数组,例如IntStream, LongStream,DoubleStream

However, only int, long, and double have their Stream types. That is to say, if the primitive arrays we want to concatenate have type int[], long[], or double[], we can pick the right Stream class and invoke the concat() method.

然而,只有intlongdouble有其Stream类型。也就是说,如果我们想要连接的原始数组的类型是int[]long[]double[],我们可以选择正确的Stream类并调用concat()方法。

Let’s see an example to concatenate two int[] arrays using IntStream:

让我们看一个例子,使用IntStream连接两个int[]数组。

static int[] concatIntArraysWithIntStream(int[] array1, int[] array2) {
    return IntStream.concat(Arrays.stream(array1), Arrays.stream(array2)).toArray();
}

As the method above shows, the Arrays.stream(int[]) method will return an IntStream object.

正如上面的方法所示,Arrays.stream(int[])方法将返回一个IntStream对象。

Also, the IntStream.toArray() method returns int[]. Therefore, we don’t need to take care of the type conversions.

另外,IntStream.toArray()方法返回int[]。因此,我们不需要照顾到类型转换的问题。

As usual, let’s create a test to see if it works with our int[] input data:

像往常一样,让我们创建一个测试,看看它是否与我们的int[]输入数据一起工作。

@Test
public void givenTwoIntArrays_whenConcatWithIntStream_thenGetExpectedResult() {
    int[] intResult = ArrayConcatUtil.concatIntArraysWithIntStream(intArray1, intArray2);
    assertThat(intResult).isEqualTo(expectedIntArray);
}

If we run the test, it’ll pass.

如果我们运行测试,它将通过。

6. Using the Apache Commons Lang Library

6.使用Apache Commons Lang Library

The Apache Commons Lang library is widely used in Java applications in the real world.

Apache Commons Lang库在现实世界的Java应用中被广泛使用。

It ships with an ArrayUtils class, which contains many handy array helper methods.

它带有一个ArrayUtils类,它包含许多方便的数组辅助方法。

The ArrayUtils class provides a series of addAll() methods, which support concatenating both non-primitive and primitive arrays.

ArrayUtils类提供了一系列addAll()方法,支持连接非原始和原始数组。

Let’s verify it by a test method:

让我们通过一个测试方法来验证它。

@Test
public void givenTwoArrays_whenConcatWithCommonsLang_thenGetExpectedResult() {
    String[] result = ArrayUtils.addAll(strArray1, strArray2);
    assertThat(result).isEqualTo(expectedStringArray);

    int[] intResult = ArrayUtils.addAll(intArray1, intArray2);
    assertThat(intResult).isEqualTo(expectedIntArray);
}

Internally, the ArrayUtils.addAll() methods use the performant System.arraycopy() method to do the array concatenation.

在内部,ArrayUtils.addAll()方法使用高性能的System.arraycopy()方法来进行数组连接。

7. Using the Guava Library

7.使用Guava库

Similar to the Apache Commons library, Guava is another library loved by many developers.

与Apache Commons库类似,Guava是另一个受到许多开发人员喜爱的库。

Guava provides convenient helper classes to do array concatenation as well.

Guava也提供了方便的辅助类来做数组连接。

If we want to concatenate non-primitive arrays, the ObjectArrays.concat() method is a good choice:

如果我们想连接非原始数组,ObjectArrays.concat()方法是一个不错的选择。

@Test
public void givenTwoStringArrays_whenConcatWithGuava_thenGetExpectedResult() {
    String[] result = ObjectArrays.concat(strArray1, strArray2, String.class);
    assertThat(result).isEqualTo(expectedStringArray);
}

Guava has offered primitive utilities for each primitive. All primitive utilities provide concat() method to concatenate the arrays with the corresponding types, for example:

Guava已经为每个基元提供了基元实用程序。所有的原始工具都提供了一个concat()方法来连接具有相应类型的数组,比如说。

  • int[] – Guava: Ints.concat(int[] … arrays)
  • long[] – Guava: Longs.concat(long[] … arrays)
  • byte[] – Guava: Bytes.concat(byte[] … arrays)
  • double[] – Guava: Doubles.concat(double[] … arrays)

We can just pick the right primitive utility class to concatenate primitive arrays.

我们只需挑选合适的原始实用类来连接原始数组。

Next, let’s concatenate our two int[] arrays using the Ints.concat() method:

接下来,让我们使用Ints.concat()方法将两个int[]数组连接起来。

@Test
public void givenTwoIntArrays_whenConcatWithGuava_thenGetExpectedResult() {
    int[] intResult = Ints.concat(intArray1, intArray2);
    assertThat(intResult).isEqualTo(expectedIntArray);
}

Similarly, Guava internally uses System.arraycopy() in the above-mentioned methods to do the array concatenation to gain good performance.

同样地,Guava内部在上述方法中使用System.arraycopy()来做数组连接,以获得良好的性能。

8. Conclusion

8.结语

In this article, we’ve addressed different approaches to concatenate two arrays in Java through examples.

在这篇文章中,我们通过实例解决了在Java中连接两个数组的不同方法。

As usual, the complete code samples that accompany this article are available over on GitHub.

像往常一样,本文所附的完整代码样本可在GitHub上获得