合併Java中的兩個數組
- java
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 Collections
當我們研究這個問題時,可能會找到一個快速的解決方案。
好吧,Java沒有提供連接數組的輔助方法。但是,從Java 5開始, Collections
實用程序類引入了addAll(Collection<? super T> c, T… elements)
方法。
我們可以創建一個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);
}
在上面的方法中,我們使用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
,然後將每個數組的元素複製到result
數組。
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
對象創建通用數組實例,並將參數數組複製到result
數組。它與以前的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 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));
}
首先,我們將兩個輸入數組轉換為Stream
對象。其次,我們使用Stream.concat()
方法Stream
最後,我們返回一個包含級聯Stream.
接下來,讓我們構建一個簡單的測試方法來檢查解決方案是否有效:
@Test
public void givenTwoStringArrays_whenConcatWithStream_thenGetExpectedResult() {
String[] result = ArrayConcatUtil.concatWithStream(strArray1, strArray2);
assertThat(result).isEqualTo(expectedStringArray);
}
如果我們通過兩個String[]
數組,則測試將通過。
可能已經註意到,我們的通用方法接受T[]
類型的參數。因此,它不適用於基本數組。
接下來,讓我們看看如何使用Java Streams連接兩個基本數組。
5.2 級聯原始數組
Stream API附帶了不同的Stream
類,這些類可以將Stream
對象轉換為相應的原始數組,例如IntStream,
[LongStream ,](https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html)
和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庫
Apache Commons 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);
}
番石榴提供了每個原始的原始實用程序。所有原始實用程序提供 一個concat()
方法,用於將數組與相應的類型連接起來,例如:
int[]
–番石榴:Ints.concat(int[] … arrays)
long[]
–番石榴:Longs.concat(long[] … arrays)
byte[]
–番石榴:Bytes.concat(byte[] … arrays)
double[]
–番石榴: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()
進行數組連接以獲得良好的性能。