- JAVA
Java Arraysクラスの使い方一覧 toString・fill他
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期連続の黒字決算。