AIによる要約
Java配列のequalsやJUnitのassertEqualsへ別々の配列を渡すと、要素が同じでも一致しません。業務コードの1次元配列はArrays.equals、JUnitの期待値比較はassertArrayEqualsを使います。この記事では同じ入力で失敗と修正後の成功を確かめ、順序・長さ違い、多次元配列、配列フィールドの防御的コピーまで整理します。


JUnitで同じ要素の配列を比較して失敗した場合は、まずassertEquals(expected, actual)をassertArrayEquals(expected, actual)へ変更します。JUnitの再現テストでは、別々に作った{1, 2}を使い、修正前の失敗と修正後の成功を同じ条件で確認できます。業務コードの条件分岐を直す場合は、先に次の表でArrays.equalsとArrays.deepEqualsを選んでください。
文字列やListではequalsで内容を比較できるため、配列も同じだと思いやすいところです。しかし配列はObjectのequalsをそのまま使い、参照が同じかを比較します。
バイト列、CSVの列、外部APIのレスポンス、暗号化前後のデータなど、業務コードでも配列比較は登場します。条件分岐だけでなく、JUnitの期待値比較やDTOのequalsにも影響するため、用途ごとのAPIを選びます。
この記事のポイント
- 配列のequalsは同じ配列参照かを比較する
- 1次元配列の内容比較はArrays.equalsを使う
- 多次元配列はArrays.deepEqualsを使う
- JUnitではassertArrayEqualsを使う
- 配列をフィールドに持つクラスではequals・hashCode・コピーを確認する
配列の比較方法を目的から選ぶ
| 確認したいこと | 使う方法 | 注意点 |
|---|---|---|
| 同じ配列オブジェクトか | a == b | 要素が同じでも別配列ならfalse |
| 1次元配列の要素と順序が同じか | Arrays.equals(a, b) | 長さ・順序も比較する |
| 配列が入れ子になっているか | Arrays.deepEquals(a, b) | 外側がObject[]になる多次元配列に使う |
| JUnitで配列の期待値を検証するか | assertArrayEquals(expected, actual) | assertEqualsへ配列を渡さない |
Java 17で以下の参照・要素・多次元比較とFileSignatureの動作を確認しました。内容が一致しているのにequalsがfalseなら、まず1次元か多次元かを見分けます。
最小例:equalsとArrays.equalsの結果を確認する

int[] a = {1, 2};
int[] b = {1, 2};
System.out.println(a.equals(b));
System.out.println(Arrays.equals(a, b));答えはfalse、trueです。a.equals(b)は参照を、Arrays.equals(a, b)は各要素を比較します。

配列のequalsは参照比較
配列型はObjectを継承していますが、内容比較用のequalsを独自実装していません。そのため、同じ配列オブジェクトを参照する場合だけtrueになります。
int[] a = {1, 2};
int[] same = a;
int[] copy = {1, 2};
System.out.println(a.equals(same)); // true
System.out.println(a.equals(copy)); // false==でも同じ参照比較になります。配列の見た目や要素数が同じでも、newや配列リテラルで別々に作れば別オブジェクトです。
1次元配列はArrays.equalsで比較する
Arrays.equalsは、長さが同じか確認した後、先頭から要素を比較します。プリミティブ配列向けとオブジェクト配列向けのオーバーロードがあります。
byte[] expected = {10, 20, 30};
byte[] actual = response.getBody();
if (!Arrays.equals(expected, actual)) {
throw new IllegalStateException("応答データが一致しません");
}配列全体を文字列へ変換して比較する方法は、表示形式へ依存し意図が伝わりません。内容比較という目的を直接表すArrays.equalsを使います。
多次元配列はArrays.deepEqualsを使う
2次元配列は、要素自体が配列です。Arrays.equalsだけでは内側の配列を参照として比較するため、内容が同じでもfalseになることがあります。
int[][] a = {{1, 2}, {3, 4}};
int[][] b = {{1, 2}, {3, 4}};
System.out.println(Arrays.equals(a, b)); // false
System.out.println(Arrays.deepEquals(a, b)); // true多次元構造を配列で持つ必要があるかも確認します。行や列に意味がある業務データなら、専用クラスやListにした方が読みやすく、検証処理も置きやすい場合があります。
nullを含む比較
Arrays.equalsは両方がnullならtrue、片方だけnullならfalseを返します。ただし、null変数に対してインスタンスメソッドのequalsを呼ぶとNullPointerExceptionです。
byte[] a = null;
byte[] b = null;
System.out.println(Arrays.equals(a, b)); // true
System.out.println(Objects.deepEquals(a, b)); // truenullを空配列と同じとみなすかは別の業務判断です。『データ未取得』と『取得結果0件』を区別するなら、比較前に状態を分けます。
JUnitではassertArrayEqualsを使う
JUnitのassertEqualsへ配列を渡すと、期待した要素比較になりません。配列専用のassertArrayEqualsを使うと、異なる要素位置も分かりやすく報告されます。
同じ要素でもassertEqualsが失敗する例と修正
検証環境はJava 17.0.8、JUnit Jupiter 5.12.2、Maven 3.9.16です。org.junit.jupiter:junit-jupiter:5.12.2をテスト依存関係に設定したJava 17のMavenプロジェクトで、次のクラス全体をsrc/test/java/ArrayComparisonTest.javaへ保存し、mvn testで実行できます。既存プロジェクトへ追加する場合は、プロジェクトで採用しているJUnitのバージョンも確認してください。
最初のテストでは、expectedとactualを別々の配列として作ります。assertEqualsはAssertionFailedErrorになるので、修正前の失敗をassertThrowsで確認しています。その直後のassertArrayEqualsは、同じ2つの配列を変更せずに比較する修正後の例です。
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ArrayComparisonTest {
@Test
void assertEqualsFailsButAssertArrayEqualsPasses() {
int[] expected = {1, 2};
int[] actual = {1, 2};
// Before: 要素が同じでも、別配列へのassertEqualsは失敗する
assertThrows(AssertionFailedError.class,
() -> assertEquals(expected, actual));
// After: 同じ入力を配列専用のアサーションで比較する
assertArrayEquals(expected, actual);
}
@Test
void differentOrderIsDetected() {
assertThrows(AssertionFailedError.class,
() -> assertArrayEquals(
new int[]{1, 2}, new int[]{2, 1}));
}
@Test
void differentLengthIsDetected() {
assertThrows(AssertionFailedError.class,
() -> assertArrayEquals(
new int[]{1, 2}, new int[]{1, 2, 3}));
}
@Test
void nestedArraysAreComparedByContents() {
int[][] expected = {{1, 2}, {3, 4}};
int[][] actual = {{1, 2}, {3, 4}};
assertFalse(Arrays.equals(expected, actual));
assertTrue(Arrays.deepEquals(expected, actual));
assertArrayEquals(expected, actual);
assertThrows(AssertionFailedError.class,
() -> assertArrayEquals(
expected, new int[][]{{1, 2}, {3, 5}}));
}
}assertThrowsは、指定した型のエラーが発生したときに成功します。修正前の失敗を検証するために囲んでいるので、このクラスのテスト結果は4件すべて成功です。元のassertEqualsが成功したという意味ではありません。修正後のassertArrayEquals(expected, actual)はassertThrowsの外にあり、配列内容が一致すればそのまま通ります。
4件の実行結果と検出した違い
| 確認した条件 | 比較の結果 | テストとして確認したこと |
|---|---|---|
別々の{1, 2} | assertEqualsはAssertionFailedError。同じ入力のassertArrayEqualsは成功 | 修正前の失敗をassertThrowsで検出し、修正後の比較も成功 |
{1, 2}と{2, 1} | assertArrayEqualsはAssertionFailedError | 順序違いを検出したのでテスト成功 |
{1, 2}と{1, 2, 3} | assertArrayEqualsはAssertionFailedError | 長さ違いを検出したのでテスト成功 |
別々の{{1, 2}, {3, 4}}と、内側の要素が違う配列 | 同じ内容ならArrays.equalsはfalse、Arrays.deepEqualsはtrue、JUnitのassertArrayEqualsは成功 | 内側の4を5にした比較ではAssertionFailedErrorも検出 |
実行結果はTests run: 4, Failures: 0, Errors: 0, Skipped: 0でした。順序・長さ・内側の要素が違うケースは、誤った配列を正しいと認めるテストではなく、「比較が不一致を検出すること」を確認するテストです。この4件は整数配列を対象にしており、小数の許容誤差や独自クラスのequalsの契約全体を検証するものではありません。
int[][]の外側はObject[]として扱えるため、JUnitではassertArrayEquals(Object[], Object[])が選ばれ、内側の配列も比較されます。JUnit 5.12.2のassertArrayEqualsのAPI仕様と、assertThrowsのAPI仕様で確認できます。Java側の多次元比較は、Arrays.deepEqualsの仕様も参照してください。
CSV分割の結果も配列専用のアサーションで比べる
@Test
void カンマ区切りの文字列を順番どおり分割する() {
String[] actual = "A,B,C".split(",", -1);
assertArrayEquals(
new String[]{"A", "B", "C"},
actual);
}順序を問わない仕様なら配列そのものが合っていない可能性があります。Setへ変換する、並べ替えて比較するなど、業務上の一致条件をテストへ反映します。
配列をフィールドに持つクラスのequals
IDEやLombokでequalsを生成しても、配列フィールドの扱いを確認します。内容比較とhashCodeが対応していないと、SetやHashMapで見つからない問題につながります。
import java.util.Arrays;
import java.util.Objects;
final class FileSignature {
private final byte[] value;
FileSignature(byte[] value) {
this.value = Objects.requireNonNull(value, "value").clone();
}
@Override
public boolean equals(Object obj) {
return obj instanceof FileSignature other
&& Arrays.equals(value, other.value);
}
@Override
public int hashCode() {
return Arrays.hashCode(value);
}
}この例はコンストラクタでnullを拒否し、clone()で配列をコピーしています。呼び出し元が元配列を書き換えても、Set登録後のequals・hashCodeが変わりません。getterを追加する場合も内部配列をそのまま返さず、コピーを返します。instanceof FileSignature otherの構文を使うため、このコードはJava 17を前提にしています。
比較条件をテストで固定する
同じ内容、異なる順序、長さ違い、null、空配列、多次元というケースを用意します。大きな配列では、失敗時に全データをログへ出して機密情報やログ容量の問題を起こさないようにします。
assertTrue(Arrays.equals(new int[]{1, 2}, new int[]{1, 2}));
assertFalse(Arrays.equals(new int[]{1, 2}, new int[]{2, 1}));
assertFalse(Arrays.equals(new int[]{1}, new int[]{1, 2}));
assertTrue(Arrays.equals(new int[]{}, new int[]{}));現場レビューでよくある指摘
配列比較のレビューでは、equalsという名前だけで判断せず、参照、要素、順序、深さのどれを一致条件にするか確認します。
// レビューコメント例
配列のequalsは要素比較になりません。
1次元配列の内容を比べるならArrays.equalsを使ってください。
// レビューコメント例
これは2次元配列なので、Arrays.equalsでは内側の配列を参照比較します。
Arrays.deepEqualsまたはデータ構造の見直しを検討してください。
// レビューコメント例
テストのassertEqualsでは配列内容を比較できません。
assertArrayEqualsへ変更し、順序違いも追加してください。『同じ』の意味が参照一致なのか、要素と順序の一致なのかをレビュー返信で明確にします。比較APIの置換だけでなく、DTOやテストの契約までそろえます。
提出前のセルフチェック
レビュー前に確認すること
- 参照と内容のどちらを比較するか決めたか
- 1次元配列にArrays.equalsを使ったか
- 多次元配列にdeepEqualsが必要か
- nullと空配列を区別したか
- 順序を一致条件に含めるか
- JUnitでassertArrayEqualsを使ったか
- equalsとhashCodeが対応しているか
- 配列の外部変更を防ぐ必要があるか
equalsとコレクションを深める参考書
配列の比較はequalsの契約やハッシュ値ともつながります。Javaの基本を押さえた後に、Effective Javaで値オブジェクトと防御的コピーの判断を補強できます。
Effective Java 第3版
Javaらしい設計と実装の判断基準を深める定番書。
equals、例外、ジェネリクス、ラムダ式などを、設計上の原則から学べます。初学者の一冊目ではなく、実務経験後の振り返り向けです。
- Javaコードの設計判断を深めたい
- レビュー指摘の背景まで理解したい
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
仕様の確認:Java 17 API — Arrays(equals・deepEquals・hashCode)。
まとめ
Java配列のequalsは要素ではなく同じ配列オブジェクトかを比較します。1次元配列の内容比較にはArrays.equals、多次元配列にはArrays.deepEqualsを使います。
JUnitで同じ要素なのに失敗する場合は、assertEqualsからassertArrayEqualsへ変更し、同じ入力で確認します。順序・長さ・多次元の違いもテストで固定してください。配列を値オブジェクトへ持たせる場合は、equalsとhashCode、防御的コピーまで一緒に設計します。



