合併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()進行數組連接以獲得良好的性能。