はじめに
Javaの二次元配列は、表形式の値、ゲーム盤、画像のピクセル、行列、隣接行列のように、行と列で整理できるデータを扱うときに使いやすい構造です。その正体は配列の中に配列を入れた形であり、int[][]、String[][]、Person[][]のように型を決めて扱います。
これを正しく扱うには、array.lengthが行数を返し、array[i].lengthが各行の列数を返す点を押さえる必要があります。そのため、すべての行が同じ長さの矩形配列だけでなく、行ごとに長さが違うジャグ配列も想定した書き方が現実的になるのが基本です。
公式ドキュメントによれば、Javaの配列はオブジェクトであり、生成後に長さを変えられません。配列仕様はJava Language Specification: Arraysで確認でき、APIの入出力はjava.util.Arraysにも整理されています。
関連する基礎を先に補うなら、配列と併用しやすいコレクションはJava List型完全ガイド、メタ情報を扱う場面はJavaアノテーションの解説が参考になります。条件分岐の練習にはJavaでうるう年を判定する例、文字列の扱いはJavaエスケープ処理、継承後の振る舞いはJavaのオーバーライドと合わせて読むと理解がつながりますし、ここがポイントです。
- Java SE 21 / OpenJDK 21
- Apache Commons Lang 3.14.0
- 標準入力なしのコンソール実行を想定
int[][]による二次元配列の宣言、初期化、参照方法for文、拡張for文、Stream APIを使った走査方法- 行列計算、ゲーム盤、画像処理、ファイル保存、隣接行列への応用
ArrayIndexOutOfBoundsExceptionとNullPointerExceptionの避け方- カスタムクラスや外部ライブラリを組み合わせる設計の考え方
Javaの二次元配列とは
二次元配列は、配列を要素として持つ配列です。int[][] arrayという宣言は、整数の行を複数まとめた構造を表し、array[0][1]のように行番号と列番号を組み合わせて値にアクセスします。
その構造は表に近く、1行目の2列目、3行目の1列目という考え方で値を整理できます。ただし、Java内部では完全な表そのものではなく、外側の配列が内側の配列への参照を持つ形と理解すると、例外やメモリの挙動を説明しやすくなるのが目安です。
結果: 期待される状態は、指定した行数と列数を持つint型の二次元配列が生成され、各要素が0で初期化される形です。
この宣言では、newによって配列オブジェクトが作られます。intの初期値は0、booleanならfalse、参照型ならnullになるため、初期化直後の値を誤解しないことが必要になります。
基本概念の整理
これを1次元配列と比較すると、違いは添字の数にあるのがポイントです。singleDimensionArray[0]は1個の値を指しますが、array[0]は1行分の配列を指し、array[0][0]で値そのものへ到達します。
結果: 期待される状態は、5個のint値を格納できる一次元配列が生成され、各要素が0になる形です。
この配列では、使える添字は0から4までです。初心者がつまずきやすいのは、要素数が5なら最後の添字も5だと考えてしまう点で、Javaの配列添字は常に0始まりになります。
結果: 期待される状態は、initializedArray[0]が1、initializedArray[4]が5になる形です。
同様に、二次元配列も波かっこを使って初期化できるのが一般的です。{1, 2, 3}が1行分の配列になり、それを複数並べると行列のような構造を短く書けます。
基本的な二次元配列の宣言と初期化
基本的な宣言では、先に領域を作ってから各要素へ値を入れます。この方法は代入の流れが見えやすく、array[i][j]の読み方を確認する練習にもなるのが現実的です。
結果: 期待される出力は、3行3列の数値が行ごとに表示される形です。
結果: 期待される出力例は上記の通りで、代入した順序が行単位で確認できます。
このとき外側のfor文は行を進め、内側のfor文は列を進めます。System.out.printで同じ行に値を並べ、行末でSystem.out.printlnを呼ぶと表の見た目に近い出力になると整理できます。
ループを使った二次元配列の操作
これらの代入をすべて手で書くと、行数や列数が増えたときに保守しにくくなります。そのため、実装パターンとしてよく見るのは、array.lengthとarray[i].lengthを使って全要素を走査する書き方です。
結果: 期待される出力は、行番号と列番号の合計が偶数の位置に1、奇数の位置に0が並ぶ形です。
結果: 期待される出力例は上記の通りで、%による偶奇判定が格子状のパターンとして表れます。
この例では、if文で条件を分けています。条件が単純な場合は三項演算子でも書けますが、学習段階では分岐の意図が読み取りやすい形にしておくほうが扱いやすくなると理解できます。
💡 Tips: 二次元配列の走査では、固定値の3や5よりもarray.lengthとarray[i].lengthを使うと、サイズ変更時の修正漏れを減らせます。
| 用途 | 主な型 | 使う構文 | 注意点 | 代表例 |
|---|---|---|---|---|
| 表データ | String[][] | data[i][j] | 列の意味をそろえる | CSV風データ |
| 数値表 | int[][] | new int[rows][cols] | 初期値は0 | 成績表 |
| 行列加算 | int[][] | matrixA[i][j] | 行列サイズを合わせる | 数値計算 |
| 転置 | int[][] | src[j][i] | 行数と列数の入れ替え | 表の向き変更 |
| ゲーム盤 | int[][] | Random.nextInt | 乱数出力は毎回変わる | マップ生成 |
| 画像処理 | int[][] | getRGB | 座標と配列添字を対応させる | グレースケール |
| ファイル保存 | String[][] | FileWriter | 区切り文字の扱い | テキスト出力 |
| ファイル読込 | String | BufferedReader | 行単位で処理する | CSV読込 |
| Stream変換 | int[][] | Arrays.stream | 可読性を見て選ぶ | 一括変換 |
| グラフ | int[][] | addEdge | 頂点番号を管理する | 隣接行列 |
| 例外対策 | int[][] | length | 範囲外アクセスを避ける | ループ境界 |
| null対策 | int[][] | != null | 行配列の生成を確認する | ジャグ配列 |
| カスタム型 | Person[][] | new Person | 参照先の状態に注意する | 座席表 |
| コピー | int[][] | ArrayUtils.clone | 浅いコピーとの差を把握する | 配列複製 |
| 表示 | int[][] | System.out.print | 改行位置を決める | コンソール表 |
| 集計 | int[][] | for | 行合計と列合計を分ける | 売上集計 |
| 検索 | String[][] | equals | ==と混同しない | 名前検索 |
| 更新 | int[][] | = | 添字を確認する | セル更新 |
| 削除相当 | String[][] | null | 配列長は変わらない | 空欄化 |
| 可変長代替 | List<List<T>> | ArrayList | 配列と用途を分ける | 動的表 |
| 長方形配列 | int[][] | new int[3][4] | 列数が固定される | 表計算 |
| ジャグ配列 | int[][] | new int[3][] | 各行の生成が必要 | 三角形表 |
| 初期化子 | int[][] | {{1,2},{3,4}} | 読みやすい範囲にする | 固定データ |
| メソッド引数 | int[][] | method(array) | 参照渡し風の挙動に注意 | 行列関数 |
| 戻り値 | int[][] | return result | 新規配列か変更かを明確にする | 変換処理 |
| 拡張for | int[] | for (int[] row : array) | 添字が必要な処理には不向き | 表示処理 |
| ビット演算 | int | >> | 色成分の位置を理解する | RGB分解 |
| リソース管理 | AutoCloseable | try-with-resources | ファイルを閉じる | 保存処理 |
| ライブラリ | ArrayUtils | clone | 依存関係を管理する | 配列操作 |
| 設計判断 | class | private | 責務を分ける | グラフクラス |
二次元配列の応用例
二次元配列の基本が分かると、単なる表の保持だけでなく、変換、計算、保存、モデル化に使えるようになります。特に押さえたいのは、処理の対象が行単位なのか、列単位なのか、セル単位なのかを先に決めることです。
行列の加算
行列の加算では、同じ位置にある要素同士を足して新しい配列に入れます。そのため、matrixAとmatrixBの行数、列数が一致していることが前提になると覚えるとよいでしょう。
結果: 期待される出力は、各セルが10になった3行3列の行列です。
結果: 期待される出力例は上記の通りで、matrixA[i][j]とmatrixB[i][j]の和がresultMatrix[i][j]へ入ります。
この実装では、元の配列を書き換えずにresultMatrixを返しています。入力を壊さない設計にしておくと、同じ配列を別の処理にも渡しやすくなると考えられます。
二次元配列のトランスポーズ
トランスポーズは、行と列を入れ替える操作です。元の配列でoriginalArray[0][1]にあった値は、転置後にtransposedArray[1][0]へ移ると考えると整理しやすくなります。
結果: 期待される状態は、3行3列のoriginalArrayに1から9までが行方向に並ぶ形です。
結果: 期待される状態は、元の列が新しい行になったtransposedArrayが作られる形です。
結果: 期待される状態は、originalArray[j][i]の値がtransposedArray[i][j]へ移され、行と列が入れ替わる形です。
結果: 期待される出力は、転置後の配列を行ごとに表示する形です。
結果: 期待される出力例は上記の通りで、元の縦方向の値が横方向に並びます。
ただし、行数と列数が違う配列ではnew int[cols][rows]のように転置後のサイズを入れ替えます。正方形だけを想定したnew int[3][3]のままでは、長方形のデータに対応できません。
ゲーム盤の作成
ゲーム盤は、セルごとに状態を持つ典型的な二次元配列の例です。0を空きマス、1を障害物のように決めると、数値だけで盤面の状態を表せますが、これは押さえたい点です。
結果: 期待される出力は、0または1が5行5列で並ぶ盤面です。Randomを使うため、具体的な値の並びは実行ごとに変わります。
結果: 期待される出力例は上記のような形式です。ただし、乱数により同じ並びが再現されるとは限りません。
この構造は、迷路、ライフゲーム、簡易シミュレーションなどに応用できます。状態が増える場合は0、1、2のような数値だけでなく、enumやクラスを使う設計も候補になると言えるでしょう。
画像処理の基礎
画像は横方向のx座標と縦方向のy座標を持つため、二次元配列と相性があります。Java標準のBufferedImageではgetRGBでピクセル値を取り出し、setRGBで新しい値を書き戻せます。
結果: 期待される処理は、入力画像からグレースケール値を計算し、保存先パスに新しい画像を書き出す流れです。ファイルパスが存在しない場合や画像形式が読めない場合は例外処理に進みます。
このコードでは、grayscaleData[y][x]の順に値を保存しています。画像APIの座標はx、yの順に渡す一方、配列は行を先に置くため、yを外側、xを内側にする対応関係を崩さないことが大切になるのが基本です。
path/to/your/image.jpgとpath/to/save/grayscale_image.jpgは仮のパスです。実際のプロジェクトでは存在する入力ファイルと書き込み可能な保存先に置き換えてください。データの保存と読み込み
表形式の文字列データは、String[][]として持つとファイルへの出力を組み立てやすくなります。簡単なテキスト保存では、行ごとに要素を区切り文字で連結し、最後に改行を入れる流れになります。
結果: 期待される出力は、data.txtへ書き込まれた各行がコンソールへ表示される形です。
結果: 期待される出力例は上記の通りで、各要素の後ろにカンマが付いた行として読み込まれます。
ただし、この書き方は簡易的な形式です。値の中にカンマや改行が入る可能性がある場合は、CSVライブラリを使う、引用符のエスケープ規則を実装するなど、入力データの性質に合わせた処理が必要になります。
Stream APIとの組み合わせ
配列の変換処理を短く書きたい場合、Arrays.streamとmapを組み合わせる方法があるのが目安です。一方、添字を細かく扱う処理では通常のfor文のほうが読みやすいこともあります。
結果: 期待される状態は、numbersに3行3列の整数が初期化される形です。
結果: 期待される出力は、元の各要素を2倍にした二次元配列です。
結果: 期待される出力例は上記の通りで、mapに渡したラムダ式n -> n * 2が全要素に適用されます。
この書き方では、外側のArrays.stream(numbers)が各行を処理し、内側のArrays.stream(row)が行内の値を処理します。小さな変換には読みやすい一方、デバッグ時に途中の添字を見たい場合はループのほうが追いやすくなるのがポイントです。
グラフの隣接行列
グラフ構造では、頂点同士が接続しているかを二次元配列で表せます。頂点0と頂点1がつながっているなら、無向グラフではadjacencyMatrix[0][1]とadjacencyMatrix[1][0]の両方を1にします。
結果: 期待される出力は、5頂点の無向グラフを表す隣接行列です。
結果: 期待される出力例は上記の通りで、頂点0、1、2が互いに接続され、頂点3と4が接続されています。
一方、頂点数が非常に多く、辺が少ないグラフでは、隣接行列よりも隣接リストのほうがメモリを抑えやすくなります。配列で表すか、List<List<Integer>>で表すかは、データの密度で判断するのが一般的です。
注意点と対処法
二次元配列の不具合は、添字の範囲、行配列のnull、サイズの思い込みから起きることが多くあります。一般的に、固定値でループを回すより、配列自身のlengthを参照するほうが変更に強い書き方になります。
メモリ管理と配列形状
Javaの二次元配列は、外側の配列と内側の配列に分かれているのが現実的です。そのため、new int[100][100]のような矩形配列だけでなく、new int[100][]として行ごとに長さを変える配列も作れます。
結果: 期待される状態は、goodExampleが100行100列の矩形配列、badExampleが行ごとに列数の異なる配列になる形です。
この例のコメントでは非矩形配列を悪い例としていますが、常に避けるべき構造という意味ではありません。三角表のように行ごとの長さが自然に異なるデータでは、ジャグ配列が合う場合もあります。
ArrayIndexOutOfBoundsExceptionへの対策
ArrayIndexOutOfBoundsExceptionは、存在しない添字へアクセスしたときに発生します。特にi <= array.lengthのように等号を入れてしまうと、最後のループで範囲外になると整理できます。
結果: 期待される状態は、10行10列の各要素に行番号と列番号の積が代入される形です。array.lengthとarray[i].lengthを使うため、範囲外アクセスを避けやすくなります。
このとき、内側の条件にarray[0].lengthを使う書き方は、全行が同じ長さのときだけ成立します。行ごとに長さが異なる可能性があるなら、現在の行を示すarray[i].lengthを使うのが自然です。
NullPointerExceptionへの対策
NullPointerExceptionは、参照先がnullのままメンバーや要素へアクセスしたときに発生すると理解できます。new int[10][]のように外側だけ生成した配列では、各行はまだ生成されていません。
結果: 期待される出力は、array[0]が生成されていないためThe array row is null.というメッセージです。
ただし、値を入れたい処理なら、判定するだけでなくarray[0] = new int[10]のように行配列を生成する必要があります。判定、生成、代入の順序を分けて考えると、例外の原因を追いやすくなります。
catch (Exception e)を置くより、入力値、添字、配列生成の前提をコード上で満たすほうが原因を特定しやすくなると覚えるとよいでしょう。二次元配列のカスタマイズ方法
二次元配列はプリミティブ型だけでなく、参照型の要素も持てます。そのため、単なる数値表から、人物、座席、セル状態、設定値のような意味を持つデータ構造へ広げられます。
カスタムデータ型の使用
カスタムクラスを配列要素にすると、複数の値をひとまとまりに扱えますし、これが一つの目安です。たとえばPersonクラスにnameとageを持たせると、二次元配列を座席表や名簿のように扱えます。
結果: 期待される出力は、4人分の名前と年齢が行ごとに表示される形です。
この設計では、配列が持つのはPersonオブジェクトへの参照です。同じオブジェクトを複数のセルから参照すると、一方で変更した内容が別のセルから見える場合があるため、共有してよいデータかを確認する必要があります。
外部ライブラリを利用した拡張
外部ライブラリを使うと、標準APIだけでは書きにくい配列操作を補えます。Apache Commons LangのArrayUtilsには配列を扱うメソッドがありますが、導入時はビルドツールの依存関係とライセンスを確認すると考えられます。
結果: 期待される出力は、元の配列とコピー後の配列が別々に表示され、コピー後の先頭要素だけが10になる形です。
もっとも、二次元配列のコピーでは浅いコピーと深いコピーの違いを確認する必要があります。プリミティブ配列では値の違いを確認しやすい一方、参照型配列では内側のオブジェクトまで複製されるかが設計上の焦点になります。
pom.xml、Gradleならbuild.gradleにApache Commons Langを追加してください。使い分けると、単純な固定表はint[][]やString[][]、行数や列数が動的に変わる表はList系、意味のあるセル情報はクラス化という判断になります。二次元配列だけに寄せるより、データの変化に合わせて構造を選ぶほうが読みやすいコードになります。
まとめ
Javaの二次元配列は、型[][] 変数名で宣言し、変数名[行][列]で値にアクセスする構造です。表形式のデータをそのまま表せるため、行列、ゲーム盤、画像処理、ファイル保存、グラフ表現まで幅広い場面で使えるのが基本です。
その理解で特に押さえたいのは、外側の配列が行を持ち、各行が内側の配列として存在する点です。この性質により、array.lengthとarray[i].lengthを分けて使う必要があり、ジャグ配列では行ごとの長さを前提にしたループが求められます。
ただし、すべてのデータを二次元配列で表す必要はありません。要素の追加や削除が多いならArrayList、セルに複数の属性があるならclassやrecord、グラフが疎なら隣接リストを検討すると、コードの意図が明確になります。
基本的には、固定サイズの表を高速に扱いたい場面では二次元配列が合いるのが目安です。一方、サイズ変更や複雑な検索が中心になる場面では、配列以外のデータ構造も候補に入れると設計の選択肢が広がります。
関連記事
- Java List型完全ガイド!初心者でもマスターできる7つのステップ
- Javaアノテーションの12選!初心者から上級者まで徹底ガイド
- Javaでうるう年を判定!初心者でも分かる9ステップ解説
- Javaエスケープ処理の10ステップマスターガイド
- Javaでマスターする!オーバーライドのたった7つのステップ
※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。


