1. 概要

このチュートリアルでは、Javaで2つのアレイを連結する方法について説明します。

まず、標準のJavaAPIを使用して独自のメソッドを実装します。

次に、一般的に使用されるライブラリを使用して問題を解決する方法を見ていきます。

2. 問題の紹介

簡単な例で問題を明確に説明できます。

たとえば、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以降、 コレクションユーティリティクラスは addAll(Collection <? スーパーT>c、T…要素) 方法。

List オブジェクトを作成し、このメソッドを2回呼び出して、2つの配列をリストに追加できます。 最後に、結果のリストを配列に変換し直します。

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()メソッドを呼び出してそれを配列に変換し直します。 この手順では、Java Listオブジェクトが不要なオーバーヘッドを追加します。

次に、問題を解決するためのより効率的な方法を見つけることができるかどうかを見てみましょう。

4. 配列コピー手法の使用

Javaは配列連結メソッドを提供しませんが、 System.arraycopy() Arrays.copyOf()の2つの配列コピーメソッドを提供します。

Javaの配列コピーメソッドを使用して問題を解決できます。

アイデアは、resultlength= array1.length +array2.lengthを持つresultなどの新しい配列を作成し、各配列の要素をコピーすることです。 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コレクションを使用するアプローチよりもパフォーマンスが高くなります。

一方、この汎用メソッドは、 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.");
}

2つのパラメーターが配列である場合、それでも十分に安全ではありません。 たとえば、 Integer[]配列とString[]配列を連結する必要はありません。 したがって、2つの配列のComponentTypeが同一であることを確認する必要があります

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

タイプセーフチェックの後、 ConponentType オブジェクトを使用して汎用配列インスタンスを作成し、パラメーター配列をresult配列にコピーできます。 これは、以前の concatWithCopy()メソッドと非常によく似ています。

4.3. concatWithCopy2()メソッドのテスト

次に、新しいメソッドが期待どおりに機能するかどうかをテストしましょう。 まず、2つの非配列オブジェクトを渡し、メソッドが予期される例外を発生させるかどうかを確認します。

@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);
    }
}

上記のテストでは、2つの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()メソッドを2回呼び出しました。 まず、2つの String[]配列を渡します。 次に、2つの int[]プリミティブ配列を渡します。

テストを実行すると合格します。 これで、 concatWithCopy2()メソッドは期待どおりに機能すると言えます。

5. JavaStreamAPIの使用

使用しているJavaバージョンが8以降の場合は、 StreamAPIを使用できます。 StreamAPIを使用して問題を解決することもできます。

まず、 Arrays.stream()メソッドを使用して、配列からStreamを取得できます。 また、 Stream クラスは、静的な concat()メソッドを提供して、2つのStreamオブジェクトを連結します。

それでは、2つのアレイを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));
}

まず、2つの入力配列をStreamオブジェクトに変換します。 次に、 Stream.concat()メソッドを使用して、2つのStreamオブジェクトを連結します。

最後に、連結されたStream。内のすべての要素を含む配列を返します。

次に、ソリューションが機能するかどうかを確認するための簡単なテストメソッドを作成しましょう。

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

2つのString[] 配列に合格すると、テストに合格します。

おそらく、ジェネリックメソッドが T[]タイプのパラメーターを受け入れることに気づきました。 したがって、プリミティブ配列では機能しません。

次に、Javaストリームを使用して2つのプリミティブ配列を連結する方法を見てみましょう。

5.2. プリミティブ配列の連結

Stream APIは、 StreamオブジェクトをIntStream、 LongStream、DoubleStream

ただし、には、int、long、およびdoubleのみがストリームタイプを持っています。 つまり、連結するプリミティブ配列のタイプが int [] long [] 、または double [] の場合、 Stream クラスを右折し、 concat()メソッドを呼び出します。

IntStreamを使用して2つの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. ApacheCommonsLangライブラリの使用

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. グアバライブラリの使用

Apache Commonsライブラリと同様に、Guavaは多くの開発者に愛されているもう1つのライブラリです。

Guavaは、配列の連結を行うための便利なヘルパークラスも提供します。

非プリミティブ配列を連結する場合は、 ObjectArrays.concat()メソッドが適しています。

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

Guavaは、各プリミティブにプリミティブユーティリティを提供しています。 すべてのプリミティブユーティリティは、 a concat()メソッドを提供して、配列を対応するタイプと連結します。次に例を示します。

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

適切なプリミティブユーティリティクラスを選択して、プリミティブ配列を連結できます。

次に、 Ints.concat()メソッドを使用して、2つの int[]配列を連結しましょう。

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

同様に、Guavaは、上記のメソッドで System.arraycopy()を内部的に使用して、配列の連結を実行し、優れたパフォーマンスを実現します。

8. 結論

この記事では、例を通じてJavaの2つのアレイを連結するためのさまざまなアプローチについて説明しました。

いつものように、この記事に付属する完全なコードサンプルはGitHubで入手できます。