Javaにおける配列操作

Javaでの配列操作

1. 概要

Java開発者なら誰でも、配列操作を操作するときにクリーンで効率的なソリューションを作成することは必ずしも簡単ではないことを知っています。 それでも、これらはJavaエコシステムの中心的な部分であり、何度か対処する必要があります。

このため、「チートシート」を用意しておくとよいでしょう。これは、パズルにすばやく取り組むのに役立つ最も一般的な手順の概要です。 このチュートリアルは、これらの状況で役立ちます。

2. 配列とヘルパークラス

先に進む前に、Javaの配列とは何か、およびその使用方法を理解しておくと役立ちます。 Javaで初めて使用する場合は、すべての基本概念をカバーしたthis previous postを確認することをお勧めします。

配列がサポートする基本操作は、ある意味で制限されていることに注意してください。 配列に関しては、比較的単純なタスクを実行するための複雑なアルゴリズムを目にすることは珍しくありません。

このため、ほとんどの操作では、ヘルパークラスとメソッドを使用して支援します。Javaによって提供されるArraysクラスと、ApacheのArrayUtilsクラスです。

後者をプロジェクトに含めるには、Apache Commonsの依存関係を追加する必要があります。


    org.apache.commons
    commons-lang3
    3.8.1

このアーティファクトon Maven Centralの最新バージョンを確認できます。

3. 配列の最初と最後の要素を取得する

これは、配列のインデックスによるアクセスの性質のおかげで、最も一般的で単純なタスクの1つです。

すべての例で使用されるint配列を宣言して初期化することから始めましょう(特に指定しない限り)。

int[] array = new int[] { 3, 5, 2, 5, 14, 4 };

配列の最初の項目がインデックス値0に関連付けられており、使用できるlength属性があることを知っていると、次の2つの要素を取得する方法を簡単に理解できます。

int firstItem = array[0];
int lastItem = array[array.length - 1];

4. 配列からランダムな値を取得する

java.util.Randomオブジェクトを使用することで、配列から任意の値を簡単に取得できます。

int anyValue = array[new Random().nextInt(array.length)];

5. 配列に新しいアイテムを追加する

ご存じのように、配列は固定サイズの値を保持します。 したがって、アイテムを追加してこの制限を超えることはできません。

まず、新しい大きな配列を宣言し、基本配列の要素を2番目の配列にコピーする必要があります。

幸い、Arraysクラスは、配列の値を新しい異なるサイズの構造体に複製するための便利なメソッドを提供します。

int[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[newArray.length - 1] = newItem;

オプションで、プロジェクトでArrayUtilsクラスにアクセスできる場合は、そのadd method (またはそのaddAllの代替)を使用して、1行のステートメントで目的を達成できます。

int[] newArray = ArrayUtils.add(array, newItem);

ご想像のとおり、このメソッドは元のarrayオブジェクトを変更しません。その出力を新しい変数に割り当てる必要があります。

6. 2つの値の間に値を挿入する

インデックス値文字のため、他の2つの間に配列にアイテムを挿入するのは簡単な作業ではありません。

Apacheはこれを典型的なシナリオと見なし、ソリューションを簡素化するためにArrayUtilsクラスにメソッドを実装しました。

int[] largerArray = ArrayUtils.insert(2, array, 77);

値を挿入するインデックスを指定する必要があります。出力は、より多くの要素を含む新しい配列になります。

最後の引数は可変引数です(a.k.a. vararg)したがって、配列に任意の数の項目を挿入できます。

7. 2つの配列を比較する

配列はObjectsであるため、equalsメソッドを提供しますが、参照の同等性のみに依存して、デフォルトの実装を使用します。

とにかく、java.util.Arraysequalsメソッドを呼び出して、2つの配列オブジェクトに同じ値が含まれているかどうかを確認できます。

boolean areEqual = Arrays.equals(array1, array2);

注:この方法はjagged arraysには有効ではありません。 多次元構造の同等性を検証する適切な方法は、Arrays.deepEqualsの方法です。

8. 配列が空かどうかを確認する

これは、配列のlength属性を使用できることを念頭に置いた、単純な割り当てです。

boolean isEmpty = array == null || array.length == 0;

さらに、ArrayUtilsヘルパークラスには、使用できるnullセーフメソッドもあります。

boolean isEmpty = ArrayUtils.isEmpty(array);

この関数は、データ構造の長さに依存します。データ構造は、nullと空のサブ配列も有効な値と見なすため、次のエッジケースに注意する必要があります。

// These are empty arrays
Integer[] array1 = {};
Integer[] array2 = null;
Integer[] array3 = new Integer[0];

// All these will NOT be considered empty
Integer[] array3 = { null, null, null };
Integer[][] array4 = { {}, {}, {} };
Integer[] array5 = new Integer[3];

9. 配列の要素をシャッフルする方法

配列内のアイテムをシャッフルするために、ArrayUtilの機能を使用できます。

ArrayUtils.shuffle(array);

これはvoidメソッドであり、配列の実際の値を操作します。

10. ボックス配列とアンボックス配列

Objectベースの配列のみをサポートするメソッドに出くわすことがよくあります。

ここでも、ArrayUtilsヘルパークラスは、プリミティブ配列のボックス化されたバージョンを取得するのに役立ちます。

Integer[] list = ArrayUtils.toObject(array);

逆の操作も可能です。

Integer[] objectArray = { 3, 5, 2, 5, 14, 4 };
int[] array = ArrayUtils.toPrimitive(objectArray);

11. 配列から重複を削除する

重複を削除する最も簡単な方法は、配列をSet実装に変換することです。

ご存知かもしれませんが、Collectionsはジェネリックスを使用しているため、プリミティブ型をサポートしていません。

このため、この例のようにオブジェクトベースの配列を処理していない場合は、最初に値をボックス化する必要があります。

// Box
Integer[] list = ArrayUtils.toObject(array);
// Remove duplicates
Set set = new HashSet(Arrays.asList(list));
// Create array and unbox
return ArrayUtils.toPrimitive(set.toArray(new Integer[set.size()]));

また、要素の順序を維持する必要がある場合は、LinkedHashSetなどの別のSet実装を使用する必要があります。

12. 配列を印刷する方法

equalsメソッドと同様に、配列のtoString関数は、Objectクラスによって提供されるデフォルトの実装を使用しますが、これはあまり役に立ちません。

ArraysクラスとArrayUtils クラスの両方に、データ構造を読み取り可能なStringに変換するための実装が付属しています。

彼らが使用するわずかに異なる形式は別として、最も重要な違いは、多次元オブジェクトの扱い方です。

Java Utilのクラスは、使用できる2つの静的メソッドを提供します。

  • toString:ギザギザの配列ではうまく機能しません

  • deepToStringObjectベースの配列をサポートしますが、プリミティブ配列引数を使用してコンパイルしません

一方、Apache’s implementation offers a single toString method that works correctly in any case:

String arrayAsString = ArrayUtils.toString(array);

13. 配列を別の型にマッピングする

多くの場合、すべての配列アイテムに操作を適用して、それらを別のタイプのオブジェクトに変換すると便利です。

この目的を念頭に置いて、we’ll try to create a flexible helper method using Generics:

public static  U[] mapObjectArray(
  T[] array, Function function,
  Class targetClazz) {
    U[] newArray = (U[]) Array.newInstance(targetClazz, array.length);
    for (int i = 0; i < array.length; i++) {
        newArray[i] = function.apply(array[i]);
    }
    return newArray;
}

プロジェクトでJava8を使用しない場合は、Function引数を破棄して、実行する必要のあるマッピングごとにメソッドを作成できます。

これで、汎用メソッドをさまざまな操作に再利用できます。 これを説明するために、2つのテストケースを作成しましょう。

@Test
public void whenMapArrayMultiplyingValues_thenReturnMultipliedArray() {
    Integer[] multipliedExpectedArray = new Integer[] { 6, 10, 4, 10, 28, 8 };
    Integer[] output =
      MyHelperClass.mapObjectArray(array, value -> value * 2, Integer.class);

    assertThat(output).containsExactly(multipliedExpectedArray);
}

@Test
public void whenMapDividingObjectArray_thenReturnMultipliedArray() {
    Double[] multipliedExpectedArray = new Double[] { 1.5, 2.5, 1.0, 2.5, 7.0, 2.0 };
    Double[] output =
      MyHelperClass.mapObjectArray(array, value -> value / 2.0, Double.class);

    assertThat(output).containsExactly(multipliedExpectedArray);
}

プリミティブ型の場合、最初に値をボックス化する必要があります。

別の方法として、Java 8’s Streamsを使用してマッピングを実行することもできます。

まず、配列をObjectsのStreamに変換する必要があります。 Arrays.streamメソッドを使用してこれを行うことができます。

たとえば、int値をカスタムString表現にマッピングする場合は、次のように実装します。

String[] stringArray = Arrays.stream(array)
  .mapToObj(value -> String.format("Value: %s", value))
  .toArray(String[]::new);

14. 配列の値をフィルター処理する

コレクションから値を除外することは、複数回実行する必要がある一般的なタスクです。

これは、値を受け取る配列を作成するときに、最終的なサイズがわからないためです。 したがって、we’ll rely on the Streams approach again.

配列からすべての奇数を削除したいと想像してください。

int[] evenArray = Arrays.stream(array)
  .filter(value -> value % 2 == 0)
  .toArray();

16. 結論

配列はJavaのコア機能の1つであるため、配列がどのように機能するかを理解し、配列で何ができるか、何ができないかを理解することが非常に重要です。

このチュートリアルでは、一般的なシナリオで配列操作を適切に処理する方法を学びました。

いつものように、実際の例の完全なソースコードは、our Github repoで入手できます。