- JAVA
Java二次元配列の使い方|初期化・ループ・lengthをコード付きで解説
Javaの二次元配列とは、「配列を要素に持つ配列」のことで、int[][] arr = new int[3][4]; のように角かっこを2つ並べて宣言します。 行と列を持つ表のようなデータを、arr[行][列] の形で読み書きできます。行数は arr.length、ある行の列数は arr[i].length で取得します。
この記事では、宣言と初期化の書き方4通り、length の意味、for・拡張for・Arrays.deepToString を使った出力、行ごとに長さが違うジャグ配列、例外の再現、コピーの落とし穴、合計・転置・ソート、三次元配列までを扱います。コードはすべて OpenJDK 21.0.10 で実行し、出力をそのまま掲載しています。ファイル名を合わせて java ファイル名.java で動かせます。
二次元配列の早見表
| やりたいこと | 書き方 |
|---|---|
| 値を並べて初期化する | int[][] a = { {1, 2, 3}, {4, 5, 6} }; |
| 行数と列数を決めて作る(中身は0) | int[][] a = new int[2][3]; |
| 行数だけ決めて作る | int[][] a = new int[2][]; のあと a[0] = new int[3]; |
| 要素を読む・書く | int v = a[1][2]; / a[1][2] = 10; |
| 行数を調べる | a.length |
| i行目の列数を調べる | a[i].length |
| 二重ループで回す | for (int i = 0; i < a.length; i++) for (int j = 0; j < a[i].length; j++) |
| 拡張forで回す | for (int[] row : a) for (int v : row) |
| 全体を1行で出力する | System.out.println(Arrays.deepToString(a)); |
| 行ごとに出力する | for (int[] row : a) System.out.println(Arrays.toString(row)); |
| 深いコピーを作る | Arrays.stream(a).map(int[]::clone).toArray(int[][]::new) |
| 中身を比べる | Arrays.deepEquals(a, b) |
| 行単位で並べ替える | Arrays.sort(a, Comparator.comparingInt(row -> row[0])); |
| 全要素の合計を出す | Arrays.stream(a).flatMapToInt(Arrays::stream).sum() |
| 三次元配列を作る | int[][][] c = new int[2][3][4]; |
Arrays を使うコードでは、先頭に import java.util.Arrays; を書きます。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java二次元配列の使い方|初期化・ループ・lengthをコード付きで解説」
二次元配列を宣言して初期化する4通りの書き方
初期化の書き方は4通りあります。1つのプログラムで並べて実行します。
// Init.java
import java.util.Arrays;
public class Init {
public static void main(String[] args) {
// 1. 値を並べて初期化(宣言と同時のときだけ使える書き方)
int[][] a = { {1, 2, 3}, {4, 5, 6} };
// 2. new付きの初期化子(代入や引数に直接渡すときはこちら)
int[][] b = new int[][] { {1, 2, 3}, {4, 5, 6} };
// 3. 行数と列数を指定して生成(中身は既定値の0)
int[][] c = new int[2][3];
// 4. 行数だけ指定し、各行はあとから生成
int[][] d = new int[2][];
d[0] = new int[3];
d[1] = new int[] {7, 8};
System.out.println(Arrays.deepToString(a));
System.out.println(Arrays.deepToString(b));
System.out.println(Arrays.deepToString(c));
System.out.println(Arrays.deepToString(d));
}
}
実行結果:
[[1, 2, 3], [4, 5, 6]]
[[1, 2, 3], [4, 5, 6]]
[[0, 0, 0], [0, 0, 0]]
[[0, 0, 0], [7, 8]]
4通りの使い分け
| 書き方 | 向いている場面 | 補足 |
|---|---|---|
{ {1, 2}, {3, 4} } |
最初から値が決まっている | 宣言と同時のときだけ書ける |
new int[][] { {1, 2}, {3, 4} } |
宣言のあとで代入する、メソッドの引数に直接渡す | 角かっこの中に要素数は書かない |
new int[2][3] |
大きさだけ決まっていて、値はあとで入れる | 全要素が既定値で埋まる |
new int[2][] |
行ごとに長さを変えたい | 各行は null なので、使う前に行を生成する |
既定値は要素の型で決まる
new で作った配列の中身は、型ごとの既定値で埋まります。次の値は実際に Arrays.deepToString で出力して確かめたものです。
| 要素の型 | 既定値 | new 型[1][2] の出力 |
|---|---|---|
int(long・short・byte も同じ) |
0 |
[[0, 0]] |
double(float も同じ) |
0.0 |
[[0.0, 0.0]] |
boolean |
false |
[[false, false]] |
char |
空文字ではなく文字コード0の文字 | 画面には見えない文字が2つ並ぶ |
String などの参照型 |
null |
[[null, null]] |
行を省略した new int[2][] |
各行が null |
Arrays.toString で [null, null] |
初期化で出やすいコンパイルエラー
次の2つは書きたくなる形ですが、コンパイルエラーになります。
int[][] a;
a = { {1, 2}, {3, 4} }; // 宣言と別の行では { } だけの形は使えない
int[][] b = new int[][3]; // 行数を省いて列数だけ書くことはできない
コンパイル結果(抜粋):
InitErr.java:4: error: illegal start of expression
a = { {1, 2}, {3, 4} };
^
InitErr.java:5: error: ']' expected
int[][] b = new int[][3];
^
| エラーになる書き方 | 直し方 |
|---|---|
a = { {1, 2}, {3, 4} }; |
a = new int[][] { {1, 2}, {3, 4} }; |
new int[][3] |
new int[2][3] または new int[2][](左側の要素数は必ず書く) |
lengthの意味:行数と列数を取り出す
二次元配列で最初につまずきやすいのが length です。arr.length は行数、arr[i].length はi行目の列数を表します。
図で見る二次元配列の構造
二次元配列は「行を表す配列への参照を並べた配列」です。3行4列の配列は次のような構造になっています。
arr ──▶ [ arr[0] , arr[1] , arr[2] ] ← arr.length = 3(行数)
│ │ │
▼ │ │
[ 1, 2, 3, 4 ] │ ← arr[0].length = 4(0行目の列数)
▼ │
[ 5, 6, 7, 8 ] ← arr[1].length = 4
▼
[ 9, 10, 11, 12 ] ← arr[2].length = 4
arr[1][2] は「1行目(上から2番目)の、2列目(左から3番目)」= 7
表として見ると次のようになります。添字は行も列も0から始まります。
| 列0 | 列1 | 列2 | 列3 | |
|---|---|---|---|---|
| 行0 | arr[0][0] = 1 |
arr[0][1] = 2 |
arr[0][2] = 3 |
arr[0][3] = 4 |
| 行1 | arr[1][0] = 5 |
arr[1][1] = 6 |
arr[1][2] = 7 |
arr[1][3] = 8 |
| 行2 | arr[2][0] = 9 |
arr[2][1] = 10 |
arr[2][2] = 11 |
arr[2][3] = 12 |
実行して確かめる
// Len.java
public class Len {
public static void main(String[] args) {
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
System.out.println("arr.length = " + arr.length); // 行数
System.out.println("arr[0].length = " + arr[0].length); // 0行目の列数
System.out.println("arr[1][2] = " + arr[1][2]); // 1行目・2列目
System.out.println("最後の要素 = " + arr[arr.length - 1][arr[arr.length - 1].length - 1]);
int total = 0;
for (int[] row : arr) total += row.length;
System.out.println("全要素数 = " + total);
}
}
実行結果:
arr.length = 3
arr[0].length = 4
arr[1][2] = 7
最後の要素 = 12
全要素数 = 12
lengthの早見表
| 書き方 | 意味 | 上の例の値 |
|---|---|---|
arr.length |
行数(外側の配列の要素数) | 3 |
arr[0].length |
0行目の列数 | 4 |
arr[i].length |
i行目の列数 | 4 |
arr[arr.length - 1] |
最後の行 | {9, 10, 11, 12} |
行ごとの length の合計 |
全要素数 | 12 |
length はかっこを付けないフィールドです。文字列の length() やリストの size() とは書き方が違います。
ループで全要素を処理・出力する
二次元配列の出力方法を7通り並べます。用途に合うものをそのまま使えます。
// Loop.java
import java.util.Arrays;
public class Loop {
public static void main(String[] args) {
int[][] arr = { {1, 2, 3}, {4, 5, 6} };
System.out.println("--- 1. 二重for(添字が必要なとき)");
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
System.out.println("--- 2. 拡張for(読むだけのとき)");
for (int[] row : arr) {
for (int v : row) {
System.out.print(v + " ");
}
System.out.println();
}
System.out.println("--- 3. 行ごとにArrays.toString");
for (int[] row : arr) {
System.out.println(Arrays.toString(row));
}
System.out.println("--- 4. Arrays.deepToString(1行で全体)");
System.out.println(Arrays.deepToString(arr));
System.out.println("--- 5. printfで桁をそろえる");
for (int[] row : arr) {
for (int v : row) System.out.printf("%4d", v);
System.out.println();
}
System.out.println("--- 6. Streamで出力");
Arrays.stream(arr).map(Arrays::toString).forEach(System.out::println);
System.out.println("--- 7. そのままprintlnやArrays.toStringに渡した場合");
System.out.println(arr.getClass().getName());
System.out.println(Arrays.toString(arr).startsWith("[[I@"));
}
}
実行結果:
--- 1. 二重for(添字が必要なとき)
1 2 3
4 5 6
--- 2. 拡張for(読むだけのとき)
1 2 3
4 5 6
--- 3. 行ごとにArrays.toString
[1, 2, 3]
[4, 5, 6]
--- 4. Arrays.deepToString(1行で全体)
[[1, 2, 3], [4, 5, 6]]
--- 5. printfで桁をそろえる
1 2 3
4 5 6
--- 6. Streamで出力
[1, 2, 3]
[4, 5, 6]
--- 7. そのままprintlnやArrays.toStringに渡した場合
[[I
true
出力方法の選び方
| 方法 | 向いている場面 |
|---|---|
| 二重for | 添字 i・j を使う、要素を書き換える、一部だけ処理する |
| 拡張for | 全要素を順に読むだけ |
行ごとに Arrays.toString |
行と列の形が分かるように確認したい |
Arrays.deepToString |
デバッグで全体をすばやく確認したい |
printf("%4d") |
桁をそろえて表のように見せたい |
| Stream | 加工や集計と組み合わせたい |
7番目の結果のとおり、二次元配列をそのまま println に渡したり Arrays.toString に渡したりすると、[[I@1b6d3586 のような「型名+ハッシュ値」の文字列になります(@ 以降は実行のたびに変わります)。二次元配列の中身を見たいときは Arrays.deepToString を使う、と覚えておくと便利です。
内側のループ条件はarr[i].lengthにする
内側のループの条件を arr[0].length に固定すると、行ごとに長さが違う配列で範囲外になります。内側は必ず arr[i].length と書くと、どんな形の配列でも正しく回せます。
拡張forでは要素を書き換えられない
拡張forの変数は「要素のコピー」です。基本型の値を代入しても、元の配列は変わりません。
// ForEachTrap.java
import java.util.Arrays;
public class ForEachTrap {
public static void main(String[] args) {
int[][] arr = { {1, 2}, {3, 4} };
for (int[] row : arr) {
for (int v : row) {
v = 0; // コピーされた値を書き換えているだけ
}
}
System.out.println(Arrays.deepToString(arr));
for (int[] row : arr) {
row[0] = 0; // rowは行そのものを指すので反映される
}
System.out.println(Arrays.deepToString(arr));
for (int[] row : arr) {
Arrays.fill(row, 9); // 行をまとめて埋める
}
System.out.println(Arrays.deepToString(arr));
}
}
実行結果:
[[1, 2], [3, 4]]
[[0, 2], [0, 4]]
[[9, 9], [9, 9]]
| 書き方 | 元の配列に反映されるか |
|---|---|
for (int v : row) v = 0; |
反映されない |
row[0] = 0;(row は拡張forの変数) |
反映される |
Arrays.fill(row, 9); |
反映される |
arr[i][j] = 0;(二重for) |
反映される |
行ごとに長さが違うジャグ配列
Javaの二次元配列は「配列の配列」なので、行ごとに列数を変えられます。このような配列をジャグ配列(不規則配列)と呼びます。
// Jagged.java
import java.util.Arrays;
public class Jagged {
public static void main(String[] args) {
int[][] jag = { {1}, {2, 3}, {4, 5, 6} };
for (int i = 0; i < jag.length; i++) {
System.out.println("jag[" + i + "].length = " + jag[i].length);
}
System.out.println(Arrays.deepToString(jag));
// 三角形の表(パスカルの三角形)を作る
int n = 5;
int[][] tri = new int[n][];
for (int i = 0; i < n; i++) {
tri[i] = new int[i + 1];
tri[i][0] = tri[i][i] = 1;
for (int j = 1; j < i; j++) {
tri[i][j] = tri[i - 1][j - 1] + tri[i - 1][j];
}
}
for (int[] row : tri) System.out.println(Arrays.toString(row));
}
}
実行結果:
jag[0].length = 1
jag[1].length = 2
jag[2].length = 3
[[1], [2, 3], [4, 5, 6]]
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
ジャグ配列は、月ごとの日数、段ごとに個数が違う表、三角形の表など、行によって必要な列数が違うデータに向いています。使わない要素を確保せずに済むのが利点です。
例外を再現して原因を確かめる
二次元配列でよく出る例外を、実際に発生させてメッセージを確認します。
ArrayIndexOutOfBoundsException:範囲外の添字
ループの条件を <= にした例です。
// Oob.java
public class Oob {
public static void main(String[] args) {
int[][] arr = new int[2][3];
for (int i = 0; i <= arr.length; i++) { // <= になっている
for (int j = 0; j < arr[i].length; j++) {
arr[i][j] = i * 10 + j;
}
}
}
}
実行結果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
at Oob.main(Oob.java:5)
メッセージの Index 2 out of bounds for length 2 は「長さ2の配列に、添字2でアクセスした」という意味です。長さ2の配列で使える添字は0と1なので、i <= arr.length を i < arr.length に直すと解消します。
次は、ジャグ配列で内側の条件を jag[0].length に固定した例です。
// Oob2.java
public class Oob2 {
public static void main(String[] args) {
int[][] jag = { {1, 2, 3}, {4} };
for (int i = 0; i < jag.length; i++) {
for (int j = 0; j < jag[0].length; j++) { // 0行目の長さで固定している
System.out.println(jag[i][j]);
}
}
}
}
実行結果:
1
2
3
4
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at Oob2.main(Oob2.java:6)
1行目(長さ1)に対して添字1でアクセスしたため、途中で止まりました。jag[0].length を jag[i].length に直すと最後まで動きます。
NullPointerException:行を生成していない
new int[2][] で作った配列は、各行がまだ null です。
// Npe.java
public class Npe {
public static void main(String[] args) {
int[][] d = new int[2][]; // 各行はまだnull
d[0][0] = 1;
}
}
実行結果:
Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "<local1>[0]" is null
at Npe.main(Npe.java:4)
"<local1>[0]" is null は、ローカル変数の0番目(ここでは d[0])が null だという意味です。今回は java Npe.java で直接実行したため変数名が <local1> と表示されています。d[0] = new int[3]; のように行を生成してから要素に代入すると解消します。
例外と直し方の一覧
| 例外・メッセージ | 原因 | 直し方 |
|---|---|---|
ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2 |
添字が長さ以上(<= の条件、行と列の取り違えなど) |
条件を < にする。行は arr.length、列は arr[i].length |
ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1 |
ジャグ配列で列数を固定して回している | 内側の条件を arr[i].length にする |
NullPointerException: Cannot store to int array because ... is null |
new int[n][] のあと行を生成していない |
arr[i] = new int[m]; を先に実行する |
NegativeArraySizeException: -1 |
要素数に負の値を指定した | 要素数の計算結果を事前に確認する |
ClassCastException: class [I cannot be cast to class java.lang.Comparable |
Arrays.sort(二次元配列) を比較方法なしで呼んだ |
Comparator を渡す(後述) |
NegativeArraySizeException と ClassCastException も、それぞれ new int[-1][3] と Arrays.sort(data) を実行して上記のメッセージが出ることを確認しています。
コピーの落とし穴:cloneは浅いコピーになる
二次元配列を clone() や Arrays.copyOf でコピーすると、外側の配列だけが新しく作られ、各行は元の配列と共有されます(浅いコピー)。コピー先を書き換えると、元の配列も変わります。
// Copy.java
import java.util.Arrays;
public class Copy {
public static void main(String[] args) {
int[][] src = { {1, 2}, {3, 4} };
// 浅いコピー:外側の配列だけが新しくなり、行は共有される
int[][] shallow = src.clone();
shallow[0][0] = 99;
System.out.println("src = " + Arrays.deepToString(src));
System.out.println("shallow = " + Arrays.deepToString(shallow));
System.out.println("外側は別物か: " + (src != shallow));
System.out.println("0行目は同じ実体か: " + (src[0] == shallow[0]));
// Arrays.copyOfも同じく浅いコピー
int[][] copyOf = Arrays.copyOf(src, src.length);
System.out.println("copyOfの0行目は同じ実体か: " + (src[0] == copyOf[0]));
// 深いコピー1:行ごとにcloneする
src[0][0] = 1;
int[][] deep1 = new int[src.length][];
for (int i = 0; i < src.length; i++) {
deep1[i] = src[i].clone();
}
// 深いコピー2:Streamで1行にまとめる
int[][] deep2 = Arrays.stream(src).map(int[]::clone).toArray(int[][]::new);
deep1[0][0] = -1;
deep2[1][1] = -2;
System.out.println("src = " + Arrays.deepToString(src));
System.out.println("deep1 = " + Arrays.deepToString(deep1));
System.out.println("deep2 = " + Arrays.deepToString(deep2));
// 比較:==、equals、Arrays.equals、Arrays.deepEquals
int[][] x = { {1, 2}, {3, 4} };
int[][] y = { {1, 2}, {3, 4} };
System.out.println("x == y : " + (x == y));
System.out.println("x.equals(y) : " + x.equals(y));
System.out.println("Arrays.equals : " + Arrays.equals(x, y));
System.out.println("Arrays.deepEquals : " + Arrays.deepEquals(x, y));
}
}
実行結果:
src = [[99, 2], [3, 4]]
shallow = [[99, 2], [3, 4]]
外側は別物か: true
0行目は同じ実体か: true
copyOfの0行目は同じ実体か: true
src = [[1, 2], [3, 4]]
deep1 = [[-1, 2], [3, 4]]
deep2 = [[1, 2], [3, -2]]
x == y : false
x.equals(y) : false
Arrays.equals : false
Arrays.deepEquals : true
shallow[0][0] = 99 と書いただけで、src の中身も99に変わっています。一方、行ごとに clone() した deep1 と deep2 は、書き換えても src に影響しません。
コピー方法の比較表
| 方法 | コピーの深さ | 元の配列への影響 |
|---|---|---|
int[][] b = a; |
コピーではなく同じ配列を指す | すべての変更が共有される |
a.clone() |
浅い(外側だけ) | 要素の書き換えが共有される |
Arrays.copyOf(a, a.length) |
浅い(外側だけ) | 要素の書き換えが共有される |
行ごとに a[i].clone() |
深い | 影響しない |
Arrays.stream(a).map(int[]::clone).toArray(int[][]::new) |
深い | 影響しない |
二次元配列を独立させたいときは、行ごとにコピーすると覚えておきましょう。要素が String のような変更できない型であれば、行ごとのコピーで十分に独立します。要素が変更できるオブジェクトの場合は、そのオブジェクトも1つずつコピーする必要があります。
中身の比較はArrays.deepEquals
上の結果のとおり、==・equals・Arrays.equals はどれも false になりました。Arrays.equals は行同士を参照で比べるためです。二次元配列の中身を比べるときは Arrays.deepEquals を使います。
Arrays.fillで行を埋めるときの注意
同じ理由で、Arrays.fill(grid, new int[2]) と書くと、同じ1行がすべての位置に入ります。
// FillTrap.java
import java.util.Arrays;
public class FillTrap {
public static void main(String[] args) {
int[][] grid = new int[3][];
Arrays.fill(grid, new int[2]); // 同じ1行を3か所に入れている
grid[0][0] = 7;
System.out.println(Arrays.deepToString(grid));
int[][] ok = new int[3][2];
for (int[] row : ok) Arrays.fill(row, -1); // 行ごとに埋める
ok[0][0] = 7;
System.out.println(Arrays.deepToString(ok));
int[][] ok2 = new int[3][];
Arrays.setAll(ok2, i -> new int[2]); // 行ごとに新しい配列を作る
ok2[0][0] = 7;
System.out.println(Arrays.deepToString(ok2));
}
}
実行結果:
[[7, 0], [7, 0], [7, 0]]
[[7, -1], [-1, -1], [-1, -1]]
[[7, 0], [0, 0], [0, 0]]
1つ目は grid[0][0] だけを書き換えたのに、3行すべてが変わっています。0以外の値で全体を埋めたいときは、行ごとに Arrays.fill(row, 値) を呼ぶのが確実です。
行・列の合計、検索、転置をコードで
3人×4科目の点数表を使って、よく使う処理をまとめて実行します。
// Calc.java
import java.util.Arrays;
public class Calc {
public static void main(String[] args) {
// 3人×4科目の点数表
int[][] scores = {
{80, 65, 90, 70},
{55, 75, 60, 85},
{95, 88, 72, 64}
};
int rows = scores.length;
int cols = scores[0].length;
// 行ごとの合計と平均
for (int i = 0; i < rows; i++) {
int sum = 0;
for (int v : scores[i]) sum += v;
System.out.printf("行%d 合計=%d 平均=%.2f%n", i, sum, (double) sum / cols);
}
// 列ごとの合計
int[] colSum = new int[cols];
for (int[] row : scores) {
for (int j = 0; j < cols; j++) colSum[j] += row[j];
}
System.out.println("列合計 = " + Arrays.toString(colSum));
// 全体の合計・最大・最小(Stream)
int total = Arrays.stream(scores).flatMapToInt(Arrays::stream).sum();
int max = Arrays.stream(scores).flatMapToInt(Arrays::stream).max().getAsInt();
int min = Arrays.stream(scores).flatMapToInt(Arrays::stream).min().getAsInt();
System.out.println("総合計=" + total + " 最大=" + max + " 最小=" + min);
// 行ごとの合計をStreamで
int[] rowSum = Arrays.stream(scores).mapToInt(r -> Arrays.stream(r).sum()).toArray();
System.out.println("行合計 = " + Arrays.toString(rowSum));
// 値を探して位置を返す
int target = 88;
search:
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (scores[i][j] == target) {
System.out.println(target + " は [" + i + "][" + j + "] にあります");
break search;
}
}
}
// 転置(行と列を入れ替える):3×4 → 4×3
int[][] t = new int[cols][rows];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
t[j][i] = scores[i][j];
}
}
for (int[] row : t) System.out.println(Arrays.toString(row));
// 1次元に平らにする
int[] flat = Arrays.stream(scores).flatMapToInt(Arrays::stream).toArray();
System.out.println(Arrays.toString(flat));
}
}
実行結果:
行0 合計=305 平均=76.25
行1 合計=275 平均=68.75
行2 合計=319 平均=79.75
列合計 = [230, 228, 222, 219]
総合計=899 最大=95 最小=55
行合計 = [305, 275, 319]
88 は [2][1] にあります
[80, 55, 95]
[65, 75, 88]
[90, 60, 72]
[70, 85, 64]
[80, 65, 90, 70, 55, 75, 60, 85, 95, 88, 72, 64]
処理ごとのポイント
| 処理 | ポイント |
|---|---|
| 行の合計 | 外側のループの中で合計用の変数を0に戻す |
| 平均 | (double) sum / cols のように、割る前に double へ変換する(整数同士の割り算は小数が切り捨てられる) |
| 列の合計 | 列数ぶんの配列を用意し、colSum[j] += row[j] で足し込む |
| 全体の集計 | flatMapToInt(Arrays::stream) で1本の IntStream にしてから sum max min |
| 検索 | 見つけたらラベル付き break で二重ループをまとめて抜ける |
| 転置 | 大きさを new int[cols][rows] にして t[j][i] = scores[i][j] |
時計回りに90度回転させたいときは、代入先を r[j][rows - 1 - i] = m[i][j] にします。{ {1, 2, 3}, {4, 5, 6} } で実行すると [4, 1] [5, 2] [6, 3] の3行になりました。
行単位でソートする
二次元配列のソートには「行の中を並べ替える」と「行そのものを並べ替える」の2種類があります。
// Sort.java
import java.util.Arrays;
import java.util.Comparator;
public class Sort {
public static void main(String[] args) {
// {ID, 点数}
int[][] data = { {3, 70}, {1, 90}, {2, 70}, {4, 55} };
// 1. 各行の中身を昇順に(行の中を並べ替える)
int[][] a = { {3, 1, 2}, {9, 7, 8} };
for (int[] row : a) Arrays.sort(row);
System.out.println("行の中を並べ替え : " + Arrays.deepToString(a));
// 2. 行単位で並べ替え:0列目(ID)の昇順
int[][] byId = Arrays.stream(data).map(int[]::clone).toArray(int[][]::new);
Arrays.sort(byId, Comparator.comparingInt(row -> row[0]));
System.out.println("0列目の昇順 : " + Arrays.deepToString(byId));
// 3. 1列目(点数)の降順
int[][] byScoreDesc = Arrays.stream(data).map(int[]::clone).toArray(int[][]::new);
Arrays.sort(byScoreDesc, (x, y) -> Integer.compare(y[1], x[1]));
System.out.println("1列目の降順 : " + Arrays.deepToString(byScoreDesc));
// 4. 点数の降順、同点ならIDの昇順
int[][] multi = Arrays.stream(data).map(int[]::clone).toArray(int[][]::new);
Arrays.sort(multi, Comparator.<int[]>comparingInt(row -> row[1]).reversed()
.thenComparingInt(row -> row[0]));
System.out.println("点数降順→ID昇順 : " + Arrays.deepToString(multi));
// 5. 文字列の二次元配列を名前順に
String[][] members = { {"sato", "tokyo"}, {"abe", "osaka"}, {"kato", "nagoya"} };
Arrays.sort(members, Comparator.comparing(row -> row[0]));
System.out.println("名前順 : " + Arrays.deepToString(members));
}
}
実行結果:
行の中を並べ替え : [[1, 2, 3], [7, 8, 9]]
0列目の昇順 : [[1, 90], [2, 70], [3, 70], [4, 55]]
1列目の降順 : [[1, 90], [3, 70], [2, 70], [4, 55]]
点数降順→ID昇順 : [[1, 90], [2, 70], [3, 70], [4, 55]]
名前順 : [[abe, osaka], [kato, nagoya], [sato, tokyo]]
ソートの書き方早見表
| やりたいこと | 書き方 |
|---|---|
| 各行の中身を昇順に | for (int[] row : a) Arrays.sort(row); |
| 0列目の昇順で行を並べ替え | Arrays.sort(a, Comparator.comparingInt(row -> row[0])); |
| 1列目の降順で行を並べ替え | Arrays.sort(a, (x, y) -> Integer.compare(y[1], x[1])); |
| 複数の列で並べ替え | Comparator.<int[]>comparingInt(row -> row[1]).reversed().thenComparingInt(row -> row[0]) |
| 文字列の列で並べ替え | Arrays.sort(s, Comparator.comparing(row -> row[0])); |
3番目の結果では、同じ70点の行が元の順番(ID 3 → ID 2)のまま並んでいます。オブジェクトの配列に対する Arrays.sort は、同じ順位の要素の並びを保つ(安定な)ソートだからです。同点のときの順番も決めたい場合は、4番目のように thenComparingInt を続けます。
比較方法を渡さずに Arrays.sort(data) と書くと、行(int[])同士の大小が決められず、次の例外になります。
Exception in thread "main" java.lang.ClassCastException: class [I cannot be cast to class java.lang.Comparable ([I and java.lang.Comparable are in module java.base of loader 'bootstrap')
(中略)
at java.base/java.util.Arrays.sort(Arrays.java:1042)
at SortErr.main(SortErr.java:5)
[I は int[] を表す内部の型名です。
三次元配列と多次元配列
角かっこを3つにすると三次元配列になります。考え方は二次元と同じで、「二次元配列を要素に持つ配列」です。
// ThreeD.java
import java.util.Arrays;
public class ThreeD {
public static void main(String[] args) {
// 2階 × 3行 × 4列
int[][][] cube = new int[2][3][4];
System.out.println(cube.length); // 2
System.out.println(cube[0].length); // 3
System.out.println(cube[0][0].length); // 4
int n = 0;
for (int i = 0; i < cube.length; i++) {
for (int j = 0; j < cube[i].length; j++) {
for (int k = 0; k < cube[i][j].length; k++) {
cube[i][j][k] = n++;
}
}
}
System.out.println(Arrays.deepToString(cube));
System.out.println(cube[1][2][3]);
int[][][] lit = { { {1, 2}, {3, 4} }, { {5, 6}, {7, 8} } };
for (int[][] layer : lit) {
for (int[] row : layer) {
System.out.print(Arrays.toString(row) + " ");
}
System.out.println();
}
}
}
実行結果:
2
3
4
[[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]
23
[1, 2] [3, 4]
[5, 6] [7, 8]
| 次元 | 宣言 | lengthの取り方 | ループの深さ |
|---|---|---|---|
| 一次元 | int[] a |
a.length |
1重 |
| 二次元 | int[][] a |
a.length、a[i].length |
2重 |
| 三次元 | int[][][] a |
a.length、a[i].length、a[i][j].length |
3重 |
Arrays.deepToString と Arrays.deepEquals は、三次元以上でもそのまま使えます。次元が増えるほど添字の意味が読み取りにくくなるので、三次元以上になるときは、クラスやレコードで意味のある名前を付けて表す方法も検討すると、読みやすいコードになります。
二次元配列とListのListの使い分け
行や列の数があとから変わる場合は、List<List<Integer>>(リストのリスト)が向いています。
// Lists.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Lists {
public static void main(String[] args) {
// 行も列もあとから増やせる
List<List<Integer>> table = new ArrayList<>();
table.add(new ArrayList<>(List.of(1, 2, 3)));
table.add(new ArrayList<>(List.of(4, 5)));
table.get(1).add(6); // 1行目に列を追加
table.add(new ArrayList<>()); // 空の行を追加
table.get(2).add(7);
System.out.println(table);
System.out.println("行数=" + table.size() + " 0行目の列数=" + table.get(0).size());
System.out.println("table.get(1).get(2) = " + table.get(1).get(2));
// 二次元配列 → List<List<Integer>>
int[][] arr = { {1, 2}, {3, 4} };
List<List<Integer>> fromArr = Arrays.stream(arr)
.map(row -> Arrays.stream(row).boxed().toList())
.toList();
System.out.println(fromArr);
// List<List<Integer>> → 二次元配列
int[][] toArr = table.stream()
.map(row -> row.stream().mapToInt(Integer::intValue).toArray())
.toArray(int[][]::new);
System.out.println(Arrays.deepToString(toArr));
}
}
実行結果:
[[1, 2, 3], [4, 5, 6], [7]]
行数=3 0行目の列数=3
table.get(1).get(2) = 6
[[1, 2], [3, 4]]
[[1, 2, 3], [4, 5, 6], [7]]
(Stream の toList() は Java 16以降で使えます。)
使い分けの比較表
| 比べる点 | 二次元配列 int[][] |
List<List<Integer>> |
|---|---|---|
| 大きさ | 作成時に決まり、あとから変えられない | 行も列もあとから増減できる |
| 要素の型 | 基本型(int など)をそのまま持てる |
Integer などのラッパー型 |
| 要素の読み書き | a[i][j] |
list.get(i).get(j) / list.get(i).set(j, v) |
| 行数・列数 | a.length / a[i].length |
list.size() / list.get(i).size() |
| 出力 | Arrays.deepToString(a) |
System.out.println(list) でそのまま読める |
| 中身の比較 | Arrays.deepEquals(a, b) |
list.equals(other) |
| メモリと速度 | 基本型をそのまま持つので軽い | 要素ごとにオブジェクトを持つ |
| 向いている用途 | 盤面、画像、行列計算など大きさが固定の表 | 行や列の数が実行時に決まるデータ |
1行が「ID・名前・点数」のように意味の違う値の集まりなら、クラスや record を作ってその List にすると、さらに読みやすくなります。
よくある質問
Q. arr.lengthとarr[0].lengthはどう違いますか?
A. arr.length は行数(外側の配列の要素数)、arr[0].length は0行目の列数です。3行4列の配列なら、それぞれ3と4になります。行ごとに長さが違う場合に備えて、ループでは arr[i].length を使うのがおすすめです。
Q. 二次元配列をprintlnで表示すると、記号のような文字列になります。
A. 配列をそのまま println に渡すと、型名とハッシュ値を組み合わせた文字列が表示されます。中身を見たいときは System.out.println(Arrays.deepToString(arr)); を使ってください。Arrays.toString は一次元用なので、二次元配列には deepToString を使います。
まとめ
- 二次元配列は「配列の配列」。
int[][] a = { {1, 2}, {3, 4} };またはnew int[行数][列数]で作る arr.lengthが行数、arr[i].lengthがi行目の列数。内側のループ条件はarr[i].lengthにする- 出力は
Arrays.deepToString(arr)が手軽。行ごとに見たいときはArrays.toString(row) - 行ごとに長さを変えられる(ジャグ配列)。
new int[n][]のあと行を生成してから使う Index 2 out of bounds for length 2は 「長さ2の配列に添字2でアクセスした」 という意味clone()とArrays.copyOfは浅いコピー。独立させるには行ごとにコピーし、比較はArrays.deepEquals- 行単位のソートは
Arrays.sort(a, Comparator.comparingInt(row -> row[0]))。大きさが変わるデータはList<List<Integer>>を選ぶ
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java二次元配列の使い方|初期化・ループ・lengthをコード付きで解説」
株式会社RJCでは、Javaの配列やコレクションの扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。