Javaでの配列操作
1. 概要
Javaの開発者なら誰でも、配列操作を操作するときにクリーンで効率的なソリューションを作成するのは必ずしも簡単ではないことを知っています。 それでも、それらはJavaエコシステムの中心的な部分であり、何度か対処する必要があります。
このため、「チートシート」を用意しておくとよいでしょう。これは、パズルにすばやく取り組むのに役立つ最も一般的な手順の概要です。 このチュートリアルは、そのような状況で役立ちます。
2. 配列とヘルパークラス
先に進む前に、Javaの配列とは何か、およびその使用方法を理解しておくと便利です。 Javaで初めて使用する場合は、この以前の投稿を参照して、すべての基本概念を説明することをお勧めします。
アレイがサポートする基本的な操作は、ある意味で制限されていることに注意してください。 配列に関しては、複雑なアルゴリズムが比較的単純なタスクを実行するのを見るのは珍しいことではありません。
このため、ほとんどの操作では、ヘルパークラスとメソッドを使用して支援します。Javaによって提供されるArraysクラスとApacheのArrayUtilsクラスです。
後者をプロジェクトに含めるには、 ApacheCommons依存関係を追加する必要があります。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
このアーティファクトの最新バージョンは、MavenCentralで確認できます。
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メソッド(またはその addAll 代替)を使用して、 1行のステートメント:
int[] newArray = ArrayUtils.add(array, newItem);
ご想像のとおり、このメソッドは元のarrayオブジェクトを変更しません。 その出力を新しい変数に割り当てる必要があります。
6. 2つの値の間に値を挿入します
インデックス値の文字であるため、他の2つの間の配列に項目を挿入することは簡単な作業ではありません。
Apacheはこれを典型的なシナリオと見なし、ソリューションを簡素化するためにArrayUtilsクラスにメソッドを実装しました。
int[] largerArray = ArrayUtils.insert(2, array, 77);
値を挿入するインデックスを指定する必要があります。出力は、より多くの要素を含む新しい配列になります。
最後の引数は可変引数です(別名 vararg )したがって、配列に任意の数のアイテムを挿入できます。
7. 2つのアレイを比較する
配列はObjectであるため、 equals メソッドを提供しますが、参照の同等性のみに依存して、デフォルトの実装を使用します。
とにかくjava.util.Arrays‘ equals メソッドを呼び出して、2つの配列オブジェクトに同じ値が含まれているかどうかを確認できます。
boolean areEqual = Arrays.equals(array1, array2);
注:この方法は、ジャグ配列には有効ではありません。 多次元構造の同等性を検証する適切な方法は、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実装に変換することです。
ご存知かもしれませんが、 Collection はジェネリックスを使用しているため、プリミティブ型をサポートしていません。
このため、この例のようにオブジェクトベースの配列を処理していない場合は、最初に値をボックス化する必要があります。
// Box
Integer[] list = ArrayUtils.toObject(array);
// Remove duplicates
Set<Integer> set = new HashSet<Integer>(Arrays.asList(list));
// Create array and unbox
return ArrayUtils.toPrimitive(set.toArray(new Integer[set.size()]));
注:他の手法を使用して、配列とSetオブジェクトの間で変換することもできます。
また、要素の順序を維持する必要がある場合は、LinkedHashSetなどの別のSet実装を使用する必要があります。
12. 配列を印刷する方法
equals メソッドと同様に、配列の toString 関数は、 Object クラスによって提供されるデフォルトの実装を使用しますが、これはあまり便利ではありません。
ArraysクラスとArrayUtilsクラスの両方に、データ構造を読み取り可能なStringに変換するための実装が付属しています。
使用する形式がわずかに異なることを除けば、最も重要な違いは、多次元オブジェクトの処理方法です。
Java Utilのクラスは、使用できる2つの静的メソッドを提供します。
- toString :ジャグ配列ではうまく機能しません
- deepToString : Object ベースの配列をサポートしますが、プリミティブ配列引数でコンパイルしません
一方、 Apacheの実装は、どのような場合でも正しく機能する単一のtoStringメソッドを提供します。
String arrayAsString = ArrayUtils.toString(array);
13. 配列を別のタイプにマップする
多くの場合、すべての配列アイテムに操作を適用して、それらを別のタイプのオブジェクトに変換すると便利です。
この目的を念頭に置いて、ジェネリックスを使用して柔軟なヘルパーメソッドを作成しようとします:
public static <T, U> U[] mapObjectArray(
T[] array, Function<T, U> function,
Class<U> 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);
}
プリミティブ型の場合、最初に値をボックス化する必要があります。
別の方法として、 Java8のStreamsを使用して、マッピングを実行することもできます。
まず、配列をObjectのStreamに変換する必要があります。 これは、Arrays.streamメソッドを使用して行うことができます。
たとえば、 int値をカスタムString表現にマップする場合は、次のように実装します。
String[] stringArray = Arrays.stream(array)
.mapToObj(value -> String.format("Value: %s", value))
.toArray(String[]::new);
14. 配列内の値をフィルターする
コレクションから値を除外することは、複数回実行しなければならない可能性のある一般的なタスクです。
これは、値を受け取る配列を作成するときに、最終的なサイズがわからないためです。 したがって、再びStreamsアプローチに依存します。
配列からすべての奇数を削除したいとします。
int[] evenArray = Arrays.stream(array)
.filter(value -> value % 2 == 0)
.toArray();
15. その他の一般的な配列操作
もちろん、実行する必要のある配列操作は他にもたくさんあります。
このチュートリアルで示したものとは別に、専用の投稿で他の操作について詳しく説明しました。
- Java配列に値が含まれているかどうかを確認する
- Javaで配列をコピーする方法
- 配列の最初の要素を削除する
- Javaを使用した配列内の最小値と最大値の検索
- Java配列で合計と平均を見つける
- Javaで配列を反転する方法
- Javaで配列とコレクションを結合および分割する
- Javaでさまざまなタイプのコレクションを組み合わせる
- 与えられた合計になる配列内の数値のすべてのペアを検索します
- Javaでの並べ替え
- Javaの効率的な単語頻度計算機
- Javaでの挿入ソート
16. 結論
配列はJavaのコア機能の1つであるため、配列がどのように機能するかを理解し、配列で何ができるか、何ができないかを知ることが非常に重要です。
このチュートリアルでは、一般的なシナリオで配列操作を適切に処理する方法を学びました。
いつものように、実際の例の完全なソースコードは、Githubリポジトリで入手できます。