1. 概述
在本教程中,我们将讨论如何在Java中拼接两个数组。
首先,我们将使用标准Java API实现我们自己的方法。
然后,我们将看看如何使用常用库解决问题。
2. 问题简介
快速示例可以清楚地说明问题。
比方说,我们有两个数组:
String[] strArray1 = {"element 1", "element 2", "element 3"};
String[] strArray2 = {"element 4", "element 5"};
现在,我们想加入他们并获得一个新数组:
String[] expectedStringArray = {"element 1", "element 2", "element 3", "element 4", "element 5"}
此外,我们不希望我们的方法只适用于String数组,因此我们将寻找通用解决方案。
此外,我们不应该忘记原始数组的情况。如果我们的解决方案也适用于原始数组,那就太好了:
int[] intArray1 = { 0, 1, 2, 3 };
int[] intArray2 = { 4, 5, 6, 7 };
int[] expectedIntArray = { 0, 1, 2, 3, 4, 5, 6, 7 };
在本教程中,我们将介绍解决问题的不同方法。
3. 使用Java集合
当我们审视这个问题时,可能会想出一个快速的解决方案。
好吧,Java不提供拼接数组的辅助方法。但是,从Java 5开始,Collections实用程序类引入了addAll(Collection c, T… elements)方法。
我们可以创建一个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);
}
在上面的方法中,我们使用Java反射API创建了一个泛型数组实例:resultArray。
让我们编写一个测试来验证我们的方法是否有效:
@Test
public void givenTwoStringArrays_whenConcatWithList_thenGetExpectedResult() {
String[] result = ArrayConcatUtil.concatWithCollection(strArray1, strArray2);
assertThat(result).isEqualTo(expectedStringArray);
}
如果我们执行测试,它就会通过。
这种方法非常简单。但是,由于该方法接收T[]数组,因此它不支持拼接原始数组。
除此之外,它的效率很低,因为它创建了一个ArrayList对象,稍后我们调用toArray()方法将其转换回array。在此过程中,Java List对象增加了不必要的开销。
接下来,让我们看看能不能找到更高效的方法来解决这个问题。
4. 使用数组技术
Java不提供数组拼接方法,但它提供了两种数组方法:System.arraycopy()和Arrays.copyOf()。
我们可以使用Java的数组方式来解决这个问题。
这个想法是,我们创建一个新数组,比如result,它有result。length=array1.length+array2.length,并将每个数组的元素到结果数组。
4.1 非原始数组
首先,让我们看一下方法实现:
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;
}
该方法看起来很紧凑。此外,整个方法只创建了一个新的数组对象:result。
现在,让我们编写一个测试方法来检查它是否按我们预期的那样工作:
@Test
public void givenTwoStringArrays_whenConcatWithCopy_thenGetExpectedResult() {
String[] result = ArrayConcatUtil.concatWithArrayCopy(strArray1, strArray2);
assertThat(result).isEqualTo(expectedStringArray);
}
如果我们试一试,测试就会通过。
没有不必要的对象创建。因此,此方法比使用Java Collections的方法性能更高。
另一方面,这个泛型方法只接收T[]类型的参数。因此,我们不能将原始数组传递给该方法。
但是,我们可以修改该方法,使其支持原始数组。
接下来,让我们仔细看看如何添加原始数组支持。
4.2 添加原始数组支持
为了使该方法支持原始数组,我们需要将参数的类型从T[]更改为T并进行一些类型安全检查。
首先我们看一下修改后的方法:
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;
}
显然,concatWithCopy2()方法比原始版本更长。不过也不难理解。现在,让我们快速浏览一下以了解其工作原理。
由于该方法现在允许使用T类型的参数,因此我们需要确保两个参数都是数组:
if (!array1.getClass().isArray() || !array2.getClass().isArray()) {
throw new IllegalArgumentException("Only arrays are accepted.");
}
如果两个参数是数组,它仍然不够安全。例如,我们不想拼接一个Integer[]数组和一个String[]数组。因此,我们需要确保两个数组的ComponentType相同:
if (!compType1.equals(compType2)) {
throw new IllegalArgumentException("Two arrays have different types.");
}
在类型安全检查之后,我们可以使用ConponentType对象创建一个通用数组实例,并将参数数组到结果数组。它与之前的concatWithCopy()方法非常相似。
4.3 测试concatWithCopy2()方法
接下来,让我们测试一下我们的新方法是否按预期工作。首先,我们传递两个非数组对象并查看该方法是否引发了预期的异常:
@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);
}
}
在上面的测试中,我们将两个String对象传递给该方法。如果我们执行测试,它就会通过。这意味着我们得到了预期的异常。
最后,让我们构建一个测试来检查新方法是否可以拼接原始数组:
@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);
}
这一次,我们调用了两次concatWithCopy2()方法。首先,我们传递两个String[]数组。然后,我们传递两个int[]原始数组。
如果我们运行它,测试将通过。现在,我们可以说,concatWithCopy2()方法按我们预期的方式工作。
5. 使用Java Stream API
如果我们使用的Java版本是8或更高版本,则可以使用Stream API。我们也可以使用Stream API来解决这个问题。
首先,我们可以通过Arrays.stream()方法从数组中获取Stream。此外,Stream类提供了一个静态concat()方法来拼接两个Stream对象。
现在,让我们看看如何使用Stream拼接两个数组。
5.1 拼接非原始数组
使用Java Stream构建通用解决方案非常简单:
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));
}
首先,我们将两个输入数组转换为Stream对象。其次,我们使用Stream.concat()方法拼接两个Stream对象。
最后,我们返回一个包含串联Stream中所有元素的数组。
接下来,让我们构建一个简单的测试方法来检查解决方案是否有效:
@Test
public void givenTwoStringArrays_whenConcatWithStream_thenGetExpectedResult() {
String[] result = ArrayConcatUtil.concatWithStream(strArray1, strArray2);
assertThat(result).isEqualTo(expectedStringArray);
}
如果我们传递两个String[]数组,测试将通过。
可能,我们已经注意到我们的泛型方法接收T[]类型的参数。因此,它不适用于原始数组。
接下来,让我们看看如何使用Java Stream拼接两个原始数组。
5.2 拼接原始数组
Stream API提供了不同的Stream类,可以将Stream对象转换为相应的原始数组,例如IntStream、LongStream和DoubleStream。
但是,只有int、long和double有它们的Stream类型。也就是说,如果我们要拼接的原始数组的类型为int[]、long[]或double[],我们可以选择正确的Stream类并调用concat()方法。
让我们看一个使用IntStream拼接两个int[]数组的示例:
static int[] concatIntArraysWithIntStream(int[] array1, int[] array2) {
return IntStream.concat(Arrays.stream(array1), Arrays.stream(array2)).toArray();
}
如上面的方法所示,Arrays.stream(int[])方法将返回一个IntStream对象。
此外,IntStream.toArray()方法返回int[]。因此,我们不需要处理类型转换。
像往常一样,让我们创建一个测试,看看它是否适用于我们的int[]输入数据:
@Test
public void givenTwoIntArrays_whenConcatWithIntStream_thenGetExpectedResult() {
int[] intResult = ArrayConcatUtil.concatIntArraysWithIntStream(intArray1, intArray2);
assertThat(intResult).isEqualTo(expectedIntArray);
}
如果我们运行测试,它就会通过。
6. 使用Apache Commons Lang库
ApacheCommons Lang库广泛用于现实世界中的Java应用程序。
它附带一个ArrayUtils类,其中包含许多方便的数组辅助方法。
ArrayUtils类提供了一系列addAll()方法,支持拼接非原始数组和原始数组。
我们通过一个测试方法来验证一下:
@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);
}
在内部,ArrayUtils.addAll()方法使用高性能的System.arraycopy()方法进行数组拼接。
7. 使用Guava库
与Apache Commons库类似,Guava是另一个受到许多开发人员喜爱的库。
Guava还提供了方便的辅助类来进行数组拼接。
如果我们想拼接非原始数组,ObjectArrays.concat()方法是一个不错的选择:
@Test
public void givenTwoStringArrays_whenConcatWithGuava_thenGetExpectedResult() {
String[] result = ObjectArrays.concat(strArray1, strArray2, String.class);
assertThat(result).isEqualTo(expectedStringArray);
}
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)
我们可以选择正确的原始实用程序类来拼接原始数组。
接下来,让我们使用Ints.concat()方法拼接我们的两个int[]数组:
@Test
public void givenTwoIntArrays_whenConcatWithGuava_thenGetExpectedResult() {
int[] intResult = Ints.concat(intArray1, intArray2);
assertThat(intResult).isEqualTo(expectedIntArray);
}
同样,Guava在上述方法内部使用System.arraycopy()进行数组拼接,以获得良好的性能。
8. 总结
在本文中,我们通过示例介绍了在Java中拼接两个数组的不同方法。
与往常一样,本教程的完整源代码可在GitHub上获得。