java.util.Arrays とは、配列の「出力・比較・初期化・並べ替え・検索・コピー・リストやストリームへの変換」をまとめて引き受ける、static メソッドだけのユーティリティクラスです。 配列そのものには length しか用意されていないため、中身を表示したい、2つの配列が同じか調べたい、といった場面ではほぼ必ず Arrays の出番になります。

この記事では、よく使う18個のメソッドを 「用途/戻り値/元の配列を書き換えるか/追加されたバージョン」の一覧表 にまとめ、すべてにコピーして動かせるコードと実行結果を付けました。println で [I@52aa2946 のような文字が出る理由、== と equals と deepEquals の結果の違い、fill にオブジェクトを渡したときの挙動、asList に int[] を渡したときの挙動も、実際の出力で確かめながら説明します。

確認環境:Ubuntu 24.04/OpenJDK 21.0.10(java ファイル名.java の単一ファイル実行)。追加されたバージョンは、OpenJDK 21 のソースに含まれる Arrays.java の Javadoc コメント(@since)を開いて確認しました。

Arraysクラスの主要メソッド早見表

すべて import java.util.Arrays; を書いたうえで Arrays.メソッド名(...) の形で呼び出します。「元の配列」の列が「書き換える」のメソッドは、戻り値を受け取らずに引数の配列そのものが変わります。

メソッド 用途 戻り値 元の配列 追加
toString 1次元配列の中身を文字列にする String そのまま 1.5
deepToString 2次元以上の配列の中身を文字列にする String そのまま 1.5
equals 2つの配列の中身が同じか調べる boolean そのまま 1.2(範囲指定は 9)
deepEquals 2次元以上の配列の中身が同じか調べる boolean そのまま 1.5
hashCode 中身から計算したハッシュ値を得る int そのまま 1.5
deepHashCode 2次元以上の配列のハッシュ値を得る int そのまま 1.5
fill 全要素(または範囲)に同じ値を入れる なし(void) 書き換える 1.2
setAll 添字から計算した値を各要素に入れる なし(void) 書き換える 1.8
parallelPrefix 累積和などの累積計算に書き換える なし(void) 書き換える 1.8
sort 昇順(または Comparator の順)に並べ替える なし(void) 書き換える 1.2
parallelSort 複数スレッドで並べ替える なし(void) 書き換える 1.8
binarySearch 並べ替え済みの配列から値の位置を探す int(添字。無ければ負の数) そのまま 1.2(範囲指定は 1.6)
copyOf 先頭から指定の長さで新しい配列を作る 新しい配列 そのまま 1.6
copyOfRange 指定範囲を切り出して新しい配列を作る 新しい配列 そのまま 1.6
asList 配列を固定長のリストとして扱う List<T> そのまま(リスト経由の set は配列に届く) 1.2
stream 配列からストリームを作る IntStream や Stream<T> など そのまま 1.8
compare 2つの配列を辞書順で比べる int(負・0・正) そのまま 9
mismatch 最初に値が違う添字を探す int(違いが無ければ -1) そのまま 9

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java Arraysクラスの使い方一覧 toString・fill他」

やりたいことから探す逆引き表

やりたいこと 書き方 補足
配列の中身を表示したい System.out.println(Arrays.toString(a)) 2次元以上は deepToString
2つの配列が同じ中身か調べたい Arrays.equals(a, b) 2次元以上は deepEquals
全要素を同じ値で初期化したい Arrays.fill(a, -1) オブジェクトは setAll で要素ごとに作る
連番や計算結果で初期化したい Arrays.setAll(a, i -> i * i) 引数は添字
昇順に並べ替えたい Arrays.sort(a) 降順は Integer[] と Comparator
値がどこにあるか探したい Arrays.binarySearch(a, key) 先に sort しておく
配列を複製したい・長さを変えたい Arrays.copyOf(a, 長さ) 一部だけなら copyOfRange
配列をリストにしたい Arrays.asList(a) int[] は Arrays.stream(a).boxed().toList()
合計・平均・最大を求めたい Arrays.stream(a).sum() average、max、min、summaryStatistics
どこが違うか知りたい Arrays.mismatch(a, b) Java 9 以降
配列どうしの大小を決めたい Arrays.compare(a, b) Java 9 以降

配列を出力する:toString と deepToString

println で型名と@が表示される理由

配列をそのまま println に渡すと、中身ではなく [I@52aa2946 のような文字列が表示されます。これは「配列の型を表す名前」+「@」+「ハッシュ値の16進表記」で、配列が toString を独自に用意していないために出る表示です。 次のコードで、型名とハッシュ値をそれぞれ取り出して見比べてみます。

import java.util.Arrays;

public class PrintArray {
    public static void main(String[] args) {
        int[] nums = {3, 1, 2};
        String[] names = {"sato", "suzuki"};
        int[][] grid = {{1, 2}, {3, 4}};

        System.out.println(nums);                        // 型名@ハッシュ値
        System.out.println(names);
        System.out.println(nums.getClass().getName());   // [I
        System.out.println(names.getClass().getName());  // [Ljava.lang.String;
        System.out.println(Integer.toHexString(nums.hashCode()));

        System.out.println(Arrays.toString(nums));       // 中身が見える
        System.out.println(Arrays.toString(names));
        System.out.println(Arrays.toString(grid));       // 内側は型名@ハッシュ値のまま
        System.out.println(Arrays.deepToString(grid));   // 2次元以上はこちら

        int[] none = null;
        System.out.println(Arrays.toString(none));       // null
        System.out.println(Arrays.toString(new int[0])); // []
    }
}

実行結果:

[I@52aa2946
[Ljava.lang.String;@4de5031f
[I
[Ljava.lang.String;
52aa2946
[3, 1, 2]
[sato, suzuki]
[[I@67e2d983, [I@5d47c63f]
[[1, 2], [3, 4]]
null
[]

1行目の [I@52aa2946 は、3行目の型名 [I と5行目のハッシュ値 52aa2946 を @ でつないだものと一致しています。ハッシュ値は実行するたびに変わるので、手元では別の値になります。Arrays.toString に渡すと [3, 1, 2] のように中身が見える形になります。

2次元配列を toString に渡した8行目は [[I@67e2d983, [I@5d47c63f] となりました。外側の配列の要素が「内側の配列」なので、その1つひとつが再び型名@ハッシュ値で表示されるためです。2次元以上では deepToString を使うと、内側までたどって [[1, 2], [3, 4]] と表示されます。 null を渡すと例外にはならず、文字列の null が返る点も覚えておくと便利です。

型名の読み方([I、[D、[Ljava.lang.String; など)

先頭の [ が「配列」を表し、その後ろの文字が要素の型を表します。主な型で実際に出力してみます。矢印の右側が println に出る型名です。

public class ClassNames {
    public static void main(String[] args) {
        Object[] samples = {
            new int[0], new long[0], new double[0], new boolean[0],
            new char[0], new byte[0], new String[0], new int[0][0], new Integer[0]
        };
        for (Object o : samples) {
            System.out.println(o.getClass().getSimpleName() + " -> " + o.getClass().getName());
        }
        char[] cs = {'a', 'b', 'c'};
        System.out.println(cs);            // char[] だけは中身が出る
        System.out.println("cs=" + cs);    // 文字列連結にすると型名@ハッシュ値
    }
}

実行結果:

int[] -> [I
long[] -> [J
double[] -> [D
boolean[] -> [Z
char[] -> [C
byte[] -> [B
String[] -> [Ljava.lang.String;
int[][] -> [[I
Integer[] -> [Ljava.lang.Integer;
abc
cs=[C@75f9eccc

char[] だけは println に専用の受け口があり、そのまま渡すと abc と中身が出ます。ただし "cs=" + cs のように文字列連結にすると、ほかの配列と同じく [C@75f9eccc の形になります。ログに書くときは Arrays.toString(cs) を使うと確実です。

配列を比べる:== と equals と deepEquals の違い

配列の比較は、書き方によって結果が変わります。== と a.equals(b) は「同じ配列を指しているか」を調べ、Arrays.equals(a, b) は「長さと各要素が同じか」を調べます。

import java.util.Arrays;

public class EqualsDemo {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 3};
        int[] c = a;

        System.out.println("a == b               : " + (a == b));
        System.out.println("a == c               : " + (a == c));
        System.out.println("a.equals(b)          : " + a.equals(b));
        System.out.println("Arrays.equals(a, b)  : " + Arrays.equals(a, b));

        int[][] x = {{1, 2}, {3, 4}};
        int[][] y = {{1, 2}, {3, 4}};
        System.out.println("Arrays.equals(x, y)     : " + Arrays.equals(x, y));
        System.out.println("Arrays.deepEquals(x, y) : " + Arrays.deepEquals(x, y));

        System.out.println("a.hashCode()==b.hashCode()       : " + (a.hashCode() == b.hashCode()));
        System.out.println("Arrays.hashCode(a)               : " + Arrays.hashCode(a));
        System.out.println("Arrays.hashCode(b)               : " + Arrays.hashCode(b));
        System.out.println("Arrays.deepHashCode(x)==(y)      : " + (Arrays.deepHashCode(x) == Arrays.deepHashCode(y)));

        int[] p = {1, 2, 3, 9};
        int[] q = {0, 1, 2, 3};
        // Java 9: 範囲どうしの比較 (p の 0〜2 と q の 1〜3)
        System.out.println("Arrays.equals(p,0,3,q,1,4) : " + Arrays.equals(p, 0, 3, q, 1, 4));
        System.out.println("null と null : " + Arrays.equals((int[]) null, (int[]) null));
    }
}

実行結果:

a == b               : false
a == c               : true
a.equals(b)          : false
Arrays.equals(a, b)  : true
Arrays.equals(x, y)     : false
Arrays.deepEquals(x, y) : true
a.hashCode()==b.hashCode()       : false
Arrays.hashCode(a)               : 30817
Arrays.hashCode(b)               : 30817
Arrays.deepHashCode(x)==(y)      : true
Arrays.equals(p,0,3,q,1,4) : true
null と null : true
書き方 何を比べるか 1次元で同じ中身 2次元で同じ中身
a == b 同じ配列を指しているか false false
a.equals(b) 同じ配列を指しているか(== と同じ) false false
Arrays.equals(a, b) 長さと各要素(1段だけ) true false
Arrays.deepEquals(x, y) 入れ子の奥まで (Object[] 用) true

2次元配列で Arrays.equals(x, y) が false になるのは、要素である内側の配列どうしを equals(つまり参照)で比べるためです。多次元配列は deepEquals、と覚えておけば迷いません。 deepEquals の引数は Object[] なので、int[] のような1次元のプリミティブ配列は渡せません。1次元は equals、2次元以上は deepEquals と使い分けます。

ハッシュ値も同じ関係です。a.hashCode() は配列ごとに違う値になり、Arrays.hashCode(a) は中身が同じなら同じ値(上の例では 30817)になります。Java 9 では、Arrays.equals(p, 0, 3, q, 1, 4) のように範囲どうしを比べる形も追加されました。

配列を HashMap のキーにしたいときも同じ理由で、中身が同じ別の配列では取り出せません。 キーには List.of(1, 2) のようなリストを使うと、中身で比べてくれます。

同じ値で埋める:fill と setAll

fill の基本と範囲指定

fill は配列の全要素に同じ値を入れます。引数を4つにすると、「開始の添字以上・終了の添字未満」の範囲だけを埋められます。

import java.util.Arrays;

public class FillDemo {
    public static void main(String[] args) {
        int[] scores = new int[5];
        Arrays.fill(scores, -1);
        System.out.println(Arrays.toString(scores));

        Arrays.fill(scores, 1, 3, 100);   // 添字 1 以上 3 未満
        System.out.println(Arrays.toString(scores));

        // 落とし穴: オブジェクトを fill すると全要素が同じ参照
        StringBuilder[] logs = new StringBuilder[3];
        Arrays.fill(logs, new StringBuilder());
        logs[0].append("A");
        System.out.println(Arrays.toString(logs));
        System.out.println("logs[0] == logs[2] : " + (logs[0] == logs[2]));

        // 直し方: setAll で要素ごとに new する
        StringBuilder[] fixed = new StringBuilder[3];
        Arrays.setAll(fixed, i -> new StringBuilder());
        fixed[0].append("A");
        System.out.println(Arrays.toString(fixed));
        System.out.println("fixed[0] == fixed[2] : " + (fixed[0] == fixed[2]));

        // 2次元配列でも同じことが起きる
        int[][] board = new int[3][];
        Arrays.fill(board, new int[3]);
        board[0][0] = 9;
        System.out.println(Arrays.deepToString(board));

        int[][] board2 = new int[3][];
        Arrays.setAll(board2, i -> new int[3]);
        board2[0][0] = 9;
        System.out.println(Arrays.deepToString(board2));

        // 範囲が配列の外だと例外
        try {
            Arrays.fill(scores, 2, 9, 0);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e);
        }
    }
}

実行結果:

[-1, -1, -1, -1, -1]
[-1, 100, 100, -1, -1]
[A, A, A]
logs[0] == logs[2] : true
[A, , ]
fixed[0] == fixed[2] : false
[[9, 0, 0], [9, 0, 0], [9, 0, 0]]
[[9, 0, 0], [0, 0, 0], [0, 0, 0]]
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9

1〜2行目が基本の動きです。Arrays.fill(scores, 1, 3, 100) は添字 1 と 2 だけが 100 になりました。Arrays の範囲指定は、どのメソッドも「開始は含む・終了は含まない」で統一されています。 範囲が配列からはみ出すと、最後の行のように ArrayIndexOutOfBoundsException になります。

fill にオブジェクトを渡すと全要素が同じ参照になる

3〜4行目に注目してください。Arrays.fill(logs, new StringBuilder()) のあと logs[0] にだけ "A" を追加したのに、出力は [A, A, A] です。new は fill を呼ぶ前に1回だけ実行され、できあがった1個のオブジェクトが全要素に入るため、どの要素から変更しても全部に見える、という動きになります。logs[0] == logs[2] が true であることからも、同じ1個を指しているとわかります。

2次元配列でも同じです。Arrays.fill(board, new int[3]) は1本の行を3か所から共有するので、board[0][0] = 9 で3行すべての先頭が 9 になりました。

直し方は setAll です。 Arrays.setAll(fixed, i -> new StringBuilder()) はラムダ式を要素の数だけ呼ぶので、要素ごとに別のオブジェクトが作られます。結果は [A, , ] と [[9, 0, 0], [0, 0, 0], [0, 0, 0]] で、ねらいどおり独立しました。

int や String のように変更できない値は fill、StringBuilder・ArrayList・配列のように変更できるオブジェクトは setAll、と使い分けます。

setAll と parallelPrefix で計算結果を入れる

setAll のラムダ式は添字を受け取るので、連番や計算結果での初期化にも使えます。

import java.util.Arrays;

public class SetAllDemo {
    public static void main(String[] args) {
        int[] squares = new int[6];
        Arrays.setAll(squares, i -> i * i);          // 添字から値を作る
        System.out.println(Arrays.toString(squares));

        String[] labels = new String[4];
        Arrays.setAll(labels, i -> "item-" + (i + 1));
        System.out.println(Arrays.toString(labels));

        int[] sales = {10, 20, 30, 40};
        Arrays.parallelPrefix(sales, Integer::sum);  // 累積和に書き換える
        System.out.println(Arrays.toString(sales));

        double[] rates = new double[3];
        Arrays.parallelSetAll(rates, i -> (i + 1) * 0.5);
        System.out.println(Arrays.toString(rates));
    }
}

実行結果:

[0, 1, 4, 9, 16, 25]
[item-1, item-2, item-3, item-4]
[10, 30, 60, 100]
[0.5, 1.0, 1.5]

parallelPrefix は、配列を先頭からの累積結果に書き換えます。{10, 20, 30, 40} に Integer::sum を渡すと累積和 [10, 30, 60, 100] になりました。parallelSetAll は setAll を複数スレッドで行う版で、結果は同じです。

並べ替えと検索:sort と binarySearch

sort は元の配列を並べ替えます。戻り値はありません。binarySearch は「並べ替え済み」の配列から値の位置を探します。

import java.util.Arrays;
import java.util.Comparator;

public class SortDemo {
    public static void main(String[] args) {
        int[] nums = {5, 3, 9, 1, 7};
        Arrays.sort(nums);                       // 元の配列を書き換える
        System.out.println(Arrays.toString(nums));

        int[] part = {5, 3, 9, 1, 7};
        Arrays.sort(part, 1, 4);                 // 添字 1 以上 4 未満だけ
        System.out.println(Arrays.toString(part));

        Integer[] boxed = {5, 3, 9, 1, 7};
        Arrays.sort(boxed, Comparator.reverseOrder());   // 降順は Integer[] で
        System.out.println(Arrays.toString(boxed));

        // int[] のまま降順にしたいとき
        int[] desc = Arrays.stream(new int[]{5, 3, 9, 1, 7})
                .boxed().sorted(Comparator.reverseOrder())
                .mapToInt(Integer::intValue).toArray();
        System.out.println(Arrays.toString(desc));

        String[] words = {"banana", "apple", "Cherry", "fig"};
        Arrays.sort(words);
        System.out.println(Arrays.toString(words));      // 大文字が先
        Arrays.sort(words, String.CASE_INSENSITIVE_ORDER);
        System.out.println(Arrays.toString(words));
        Arrays.sort(words, Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
        System.out.println(Arrays.toString(words));

        long[] big = {30L, 10L, 20L};
        Arrays.parallelSort(big);
        System.out.println(Arrays.toString(big));

        // binarySearch は並べ替え済みの配列に使う
        int[] sorted = {1, 3, 5, 7, 9};
        System.out.println("7 の位置       : " + Arrays.binarySearch(sorted, 7));
        System.out.println("4 (無い値)     : " + Arrays.binarySearch(sorted, 4));
        System.out.println("10 (無い値)    : " + Arrays.binarySearch(sorted, 10));
        int[] unsorted = {9, 1, 7, 3, 5};
        System.out.println("未ソートで 9   : " + Arrays.binarySearch(unsorted, 9));
    }
}

実行結果:

[1, 3, 5, 7, 9]
[5, 1, 3, 9, 7]
[9, 7, 5, 3, 1]
[9, 7, 5, 3, 1]
[Cherry, apple, banana, fig]
[apple, banana, Cherry, fig]
[fig, apple, Cherry, banana]
[10, 20, 30]
7 の位置       : 3
4 (無い値)     : -3
10 (無い値)    : -6
未ソートで 9   : -6

ポイントを順に見ていきます。

  • 範囲指定の sort:Arrays.sort(part, 1, 4) は添字 1〜3 の 3, 9, 1 だけが並べ替わり、[5, 1, 3, 9, 7] になりました。
  • 降順:Comparator を渡せるのはオブジェクトの配列だけです。Integer[] にするか、4行目のようにストリームで boxed してから並べ替えて int[] に戻します。
  • parallelSort:使い方は sort と同じで、複数スレッドで並べ替えます。
  • 文字列:そのまま sort すると文字コード順になり、大文字の Cherry が小文字より前に来ました。String.CASE_INSENSITIVE_ORDER で大文字小文字を区別しない順に、Comparator.comparingInt(String::length) で長さ順にできます。 int[] に Comparator を渡すとコンパイルエラーになります。実際のメッセージ(要点を抜粋)は次のとおりです。
SortErr.java:7: error: no suitable method found for sort(int[],Comparator<T#1>)
        Arrays.sort(nums, Comparator.reverseOrder());
              ^
(中略)
    method Arrays.<T#2>sort(T#2[],Comparator<? super T#2>) is not applicable
      (inference variable T#2 has incompatible bounds
        equality constraints: int
        upper bounds: Object)

「T#2 に int は当てはめられない」という意味で、型引数にはプリミティブ型を使えないことが理由です。

binarySearch の戻り値の読み方

状況 上の実行結果 意味
値がある(7) 3 見つかった添字
値が無い(4) -3 「-(挿入位置) - 1」。挿入位置は 2
値が無い(10) -6 挿入位置は末尾の 5
並べ替えていない配列で 9 を探す -6 配列に 9 があっても見つからない

見つからないときは -1 ではなく「-(挿入位置) - 1」が返るので、判定は >= 0 で行います。 挿入位置は -(戻り値) - 1 で求められ、並び順を保ったまま値を差し込む場所として使えます。最後の行のように、並べ替えていない配列では正しい位置が得られません。binarySearch の前には sort、とセットで覚えましょう。

コピーする:copyOf と copyOfRange

copyOf は「先頭から指定した長さ」、copyOfRange は「指定した範囲」で新しい配列を作ります。元の配列は変わりません。

import java.util.Arrays;

public class CopyDemo {
    public static void main(String[] args) {
        int[] src = {1, 2, 3, 4, 5};

        int[] same = Arrays.copyOf(src, src.length);
        int[] shorter = Arrays.copyOf(src, 3);        // 先頭 3 個
        int[] longer = Arrays.copyOf(src, 7);         // 足りない分は 0
        int[] middle = Arrays.copyOfRange(src, 1, 4); // 添字 1 以上 4 未満
        System.out.println(Arrays.toString(same));
        System.out.println(Arrays.toString(shorter));
        System.out.println(Arrays.toString(longer));
        System.out.println(Arrays.toString(middle));

        same[0] = 99;                                 // コピー側を変えても元はそのまま
        System.out.println("src  = " + Arrays.toString(src));

        String[] names = {"a", "b"};
        System.out.println(Arrays.toString(Arrays.copyOf(names, 4)));  // 参照型は null で埋まる

        // 2次元配列の copyOf は外側だけの複製(内側の行は共有)
        int[][] grid = {{1, 2}, {3, 4}};
        int[][] shallow = Arrays.copyOf(grid, grid.length);
        shallow[0][0] = 99;
        System.out.println("grid = " + Arrays.deepToString(grid));

        // 行ごとに複製すると独立する
        int[][] grid2 = {{1, 2}, {3, 4}};
        int[][] deep = Arrays.stream(grid2).map(int[]::clone).toArray(int[][]::new);
        deep[0][0] = 99;
        System.out.println("grid2= " + Arrays.deepToString(grid2));
        System.out.println("deep = " + Arrays.deepToString(deep));
    }
}

実行結果:

[1, 2, 3, 4, 5]
[1, 2, 3]
[1, 2, 3, 4, 5, 0, 0]
[2, 3, 4]
src  = [1, 2, 3, 4, 5]
[a, b, null, null]
grid = [[99, 2], [3, 4]]
grid2= [[1, 2], [3, 4]]
deep = [[99, 2], [3, 4]]
  • 長さを短くすると切り詰め、長くすると初期値で埋まります。 int[] は 0、String[] は null です(3行目と6行目)。配列の長さを増やしたいときの定番の書き方です。
  • コピーした配列を書き換えても、元の src は [1, 2, 3, 4, 5] のままでした。
  • 2次元配列の copyOf は外側の配列だけを複製します。 内側の行は共有されるため、shallow[0][0] = 99 が元の grid にも表れました。行ごとに int[]::clone で複製すると、grid2 は元のまま、deep だけが変わります。

リストに変える:asList と int[] の注意点

asList は配列をリストとして扱えるようにします。便利な一方で、動きに特徴が3つあります。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class AsListDemo {
    public static void main(String[] args) {
        int[] prim = {1, 2, 3};
        var wrong = Arrays.asList(prim);              // List<int[]> になる
        System.out.println("size = " + wrong.size());
        System.out.println(wrong);

        Integer[] boxed = {1, 2, 3};
        List<Integer> ok = Arrays.asList(boxed);
        System.out.println("size = " + ok.size() + " " + ok);

        // int[] から List<Integer> を作る
        List<Integer> viaStream = Arrays.stream(prim).boxed().toList();
        System.out.println(viaStream);

        // asList のリストは元の配列とつながっている
        String[] arr = {"a", "b", "c"};
        List<String> view = Arrays.asList(arr);
        view.set(0, "X");
        System.out.println("arr  = " + Arrays.toString(arr));
        arr[1] = "Y";
        System.out.println("view = " + view);

        // 要素数は変えられない
        try {
            view.add("d");
        } catch (UnsupportedOperationException e) {
            System.out.println("add    -> " + e);
        }
        try {
            view.remove(0);
        } catch (UnsupportedOperationException e) {
            System.out.println("remove -> " + e);
        }

        // 追加・削除したいときは ArrayList に包む
        List<String> free = new ArrayList<>(Arrays.asList(arr));
        free.add("d");
        System.out.println("free = " + free + " / arr = " + Arrays.toString(arr));

        // List.of は set もできない
        try {
            List.of("a", "b").set(0, "X");
        } catch (UnsupportedOperationException e) {
            System.out.println("List.of set -> " + e);
        }
        System.out.println(Arrays.asList("x", "y", "z"));   // 可変長引数として直接渡せる
    }
}

実行結果:

size = 1
[[I@3c419631]
size = 3 [1, 2, 3]
[1, 2, 3]
arr  = [X, b, c]
view = [X, Y, c]
add    -> java.lang.UnsupportedOperationException
remove -> java.lang.UnsupportedOperationException
free = [X, Y, c, d] / arr = [X, Y, c]
List.of set -> java.lang.UnsupportedOperationException
[x, y, z]

asList(int[]) は要素1個のリストになる

1〜2行目のとおり、int[] を asList に渡すと サイズ 1 のリスト になり、表示は [[I@3c419631] でした。asList の引数は「オブジェクトの可変長引数」で、プリミティブの配列 int[] は全体で1個のオブジェクトとして受け取られるためです。できあがるのは List<int[]> です。

int[] からリストを作るときは Arrays.stream(prim).boxed().toList() を使います。 Integer[] であれば asList でそのまま3要素のリストになります。

リストと配列はつながっている

asList が返すのは、元の配列を裏側で使う固定長のリストです。view.set(0, "X") で配列の先頭が X になり、arr[1] = "Y" でリスト側も Y になりました。Javadoc にも、配列とリストの変更が互いに見えると書かれています。

add と remove は UnsupportedOperationException

固定長なので、要素数が変わる add と remove は UnsupportedOperationException になります。追加や削除をしたいときは new ArrayList<>(Arrays.asList(arr)) で包みます。 こうすると配列とのつながりも切れ、free に "d" を足しても arr は3要素のままでした。

作り方 set(置き換え) add・remove 元の配列とのつながり
Arrays.asList(arr) できる UnsupportedOperationException あり
List.of(...) UnsupportedOperationException UnsupportedOperationException なし
new ArrayList<>(Arrays.asList(arr)) できる できる なし

配列どうしの大小と違う位置:compare と mismatch(Java 9)

Java 9 で、配列を辞書順に比べる compare と、最初に違いが出る添字を返す mismatch が追加されました。

import java.util.Arrays;

public class CompareDemo {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 4};
        int[] c = {1, 2, 3};
        int[] d = {1, 2};

        System.out.println("compare(a, b)  : " + Arrays.compare(a, b));   // 負: a が小さい
        System.out.println("compare(b, a)  : " + Arrays.compare(b, a));   // 正
        System.out.println("compare(a, c)  : " + Arrays.compare(a, c));   // 0: 同じ
        System.out.println("compare(a, d)  : " + Arrays.compare(a, d));   // 長いほうが大きい
        System.out.println("compare(null,a): " + Arrays.compare(null, a));

        System.out.println("mismatch(a, b) : " + Arrays.mismatch(a, b));  // 最初に違う添字
        System.out.println("mismatch(a, c) : " + Arrays.mismatch(a, c));  // -1: 違いなし
        System.out.println("mismatch(a, d) : " + Arrays.mismatch(a, d));  // 短いほうの長さ

        String[] s1 = {"apple", "banana"};
        String[] s2 = {"apple", "cherry"};
        System.out.println("compare(s1, s2): " + Arrays.compare(s1, s2));
        System.out.println("mismatch(s1,s2): " + Arrays.mismatch(s1, s2));

        // 配列の配列を辞書順に並べる
        int[][] versions = {{1, 10, 0}, {1, 2, 5}, {1, 2}, {0, 9, 9}};
        Arrays.sort(versions, Arrays::compare);
        System.out.println(Arrays.deepToString(versions));
    }
}

実行結果:

compare(a, b)  : -1
compare(b, a)  : 1
compare(a, c)  : 0
compare(a, d)  : 1
compare(null,a): -1
mismatch(a, b) : 2
mismatch(a, c) : -1
mismatch(a, d) : 2
compare(s1, s2): -1
mismatch(s1,s2): 1
[[0, 9, 9], [1, 2], [1, 2, 5], [1, 10, 0]]
  • compare は先頭から順に比べ、最初に違った要素の大小で結果が決まります。{1, 2, 3} と {1, 2, 4} は3番目で差がつき、負の値になりました。途中まで同じなら長いほうが大きく、null はどの配列よりも小さい扱いです。
  • mismatch は最初に違う添字を返します。違いが無ければ -1、片方が途中で終わる場合は短いほうの長さ(上の例では 2)です。テストで「どこが違うのか」を知りたいときに役立ちます。
  • 最後の例のように、Arrays::compare を Comparator として渡すと「配列の配列」を辞書順に並べ替えられます。 バージョン番号のような数値の並びを整列するときに使えます。

集計する:Arrays.stream と sum・average・max

Arrays.stream は配列からストリームを作ります。int[] なら IntStream になり、sum、average、max、min がそのまま使えます。

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.stream.Collectors;

public class StreamDemo {
    public static void main(String[] args) {
        int[] scores = {72, 88, 95, 60, 81};

        System.out.println("sum     : " + Arrays.stream(scores).sum());
        System.out.println("average : " + Arrays.stream(scores).average().getAsDouble());
        System.out.println("max     : " + Arrays.stream(scores).max().getAsInt());
        System.out.println("min     : " + Arrays.stream(scores).min().getAsInt());
        System.out.println("count>=80: " + Arrays.stream(scores).filter(s -> s >= 80).count());

        IntSummaryStatistics st = Arrays.stream(scores).summaryStatistics();
        System.out.println(st);

        // 範囲を指定 (添字 1 以上 4 未満)
        System.out.println("sum(1..3): " + Arrays.stream(scores, 1, 4).sum());

        // 条件で絞って新しい配列にする
        int[] passed = Arrays.stream(scores).filter(s -> s >= 80).toArray();
        System.out.println(Arrays.toString(passed));

        // 各要素を変換
        int[] plus5 = Arrays.stream(scores).map(s -> s + 5).toArray();
        System.out.println(Arrays.toString(plus5));

        // String[] は Stream<String> になる
        String[] names = {"sato", "suzuki", "tanaka"};
        System.out.println(Arrays.stream(names).map(String::toUpperCase).collect(Collectors.joining(", ")));
        System.out.println(String.join("-", names));

        // 空配列の max は空の OptionalInt
        System.out.println(Arrays.stream(new int[0]).max());
        System.out.println(Arrays.stream(new int[0]).sum());
    }
}

実行結果:

sum     : 396
average : 79.2
max     : 95
min     : 60
count>=80: 3
IntSummaryStatistics{count=5, sum=396, min=60, average=79.200000, max=95}
sum(1..3): 243
[88, 95, 81]
[77, 93, 100, 65, 86]
SATO, SUZUKI, TANAKA
sato-suzuki-tanaka
OptionalInt.empty
0
  • 合計は Arrays.stream(scores).sum() の1行です。 for 文で変数に足し込む書き方と同じ結果を、短く書けます。
  • average、max、min は OptionalDouble や OptionalInt を返します。空の配列では値が無いためで、最後から2行目のように OptionalInt.empty になります。sum は空なら 0 です。
  • 件数・合計・最小・平均・最大を一度に求めるなら summaryStatistics が便利です。
  • Arrays.stream(scores, 1, 4) のように範囲を指定できます。
  • filter や map で加工したあと toArray で配列に戻せます。元の配列は変わりません。
  • String[] は Stream<String> になります。区切り文字でつなぐだけなら String.join("-", names) が手軽です。

つまずきやすい箇所と直し方の一覧

起きること 理由 直し方
println で [I@… と出る 配列は toString を独自に持たない Arrays.toString(a)
2次元配列が [[I@…, [I@…] と出る toString は1段だけ Arrays.deepToString(a)
同じ中身なのに equals が false a.equals(b) は参照の比較 Arrays.equals(a, b)
2次元で Arrays.equals が false 内側の配列を参照で比べる Arrays.deepEquals(x, y)
fill したら全要素が同時に変わる 1個のオブジェクトを共有している Arrays.setAll(a, i -> new ...)
Arrays.asList(int配列) のサイズが 1 int[] 全体が1要素になる Arrays.stream(a).boxed().toList()
asList のリストで add が例外 固定長のリスト new ArrayList<>(Arrays.asList(a))
binarySearch が負の値を返す 値が無い、または並べ替えていない 先に sort、判定は >= 0

よくある質問

Q. Arrays.asList と List.of はどう使い分けますか? A. 配列を一時的にリストとして扱い、要素の置き換え(set)もしたいなら asList、変更しない固定のリストがほしいなら List.of が向いています。どちらも add と remove は UnsupportedOperationException になりました。追加・削除をするなら ArrayList に包みます。

Q. Arrays.sort で降順に並べ替えるには? A. Integer[] や String[] なら Arrays.sort(a, Comparator.reverseOrder()) です。int[] には Comparator を渡せないので、Arrays.stream(a).boxed().sorted(Comparator.reverseOrder()).mapToInt(Integer::intValue).toArray() のようにストリームを経由します。

Q. compare と mismatch はどの Java から使えますか? A. Java 9 からです。OpenJDK 21 のソースの Javadoc コメントで、どちらも @since 9 と書かれていることを確認しました。

まとめ

  • 配列の中身を表示するときは Arrays.toString、2次元以上は Arrays.deepToString。 [I@… は「型名@ハッシュ値」です。
  • 中身の比較は Arrays.equals、多次元は Arrays.deepEquals。 == と a.equals(b) は参照の比較です。
  • fill は1個の値を全要素に入れる。 変更できるオブジェクトは setAll で要素ごとに作ります。
  • sort、fill、setAll、parallelPrefix は元の配列を書き換え、copyOf、copyOfRange は新しい配列を返します。
  • binarySearch は sort 済みの配列に使い、見つかったかどうかは >= 0 で判定します。
  • Arrays.asList(int配列) は要素1個のリストになる。 Arrays.stream(a).boxed().toList() を使います。
  • 合計・平均・最大は Arrays.stream から sum、average、max、summaryStatistics で求められます。 Java 9 以降は compare と mismatch も使えます。

参考・出典

  • Oracle「Java SE 21 API 仕様 java.util.Arrays」(https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html)
  • OpenJDK 21 のソース(openjdk-21-source に含まれる java.base/java/util/Arrays.java の Javadoc コメント。各メソッドの @since、asList と binarySearch の説明)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java Arraysクラスの使い方一覧 toString・fill他」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?