読み込み中...

Javaで二次元配列を使うたった9つの方法

Javaの二次元配列を示すサンプルコードのスクリーンショット Java
この記事は約32分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

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を使った走査方法
  • 行列計算、ゲーム盤、画像処理、ファイル保存、隣接行列への応用
  • ArrayIndexOutOfBoundsExceptionNullPointerExceptionの避け方
  • カスタムクラスや外部ライブラリを組み合わせる設計の考え方

Javaの二次元配列とは

二次元配列は、配列を要素として持つ配列です。int[][] arrayという宣言は、整数の行を複数まとめた構造を表し、array[0][1]のように行番号と列番号を組み合わせて値にアクセスします。

その構造は表に近く、1行目の2列目、3行目の1列目という考え方で値を整理できます。ただし、Java内部では完全な表そのものではなく、外側の配列が内側の配列への参照を持つ形と理解すると、例外やメモリの挙動を説明しやすくなるのが目安です。

int[][] array = new int[行数][列数];

結果: 期待される状態は、指定した行数と列数を持つint型の二次元配列が生成され、各要素が0で初期化される形です。

この宣言では、newによって配列オブジェクトが作られます。intの初期値は0booleanならfalse、参照型ならnullになるため、初期化直後の値を誤解しないことが必要になります。

基本概念の整理

これを1次元配列と比較すると、違いは添字の数にあるのがポイントです。singleDimensionArray[0]は1個の値を指しますが、array[0]は1行分の配列を指し、array[0][0]で値そのものへ到達します。

int[] singleDimensionArray = new int[5];

結果: 期待される状態は、5個のint値を格納できる一次元配列が生成され、各要素が0になる形です。

この配列では、使える添字は0から4までです。初心者がつまずきやすいのは、要素数が5なら最後の添字も5だと考えてしまう点で、Javaの配列添字は常に0始まりになります。

int[] initializedArray = {1, 2, 3, 4, 5};

結果: 期待される状態は、initializedArray[0]1initializedArray[4]5になる形です。

同様に、二次元配列も波かっこを使って初期化できるのが一般的です。{1, 2, 3}が1行分の配列になり、それを複数並べると行列のような構造を短く書けます。

基本的な二次元配列の宣言と初期化

基本的な宣言では、先に領域を作ってから各要素へ値を入れます。この方法は代入の流れが見えやすく、array[i][j]の読み方を確認する練習にもなるのが現実的です。

int[][] array = new int[3][3];

array[0][0] = 1;
array[0][1] = 2;
array[0][2] = 3;
array[1][0] = 4;
array[1][1] = 5;
array[1][2] = 6;
array[2][0] = 7;
array[2][1] = 8;
array[2][2] = 9;

for(int i = 0; i < 3; i++) {
    for(int j = 0; j < 3; j++) {
        System.out.print(array[i][j] + " ");
    }
    System.out.println();
}

結果: 期待される出力は、3行3列の数値が行ごとに表示される形です。

1 2 3
4 5 6
7 8 9

結果: 期待される出力例は上記の通りで、代入した順序が行単位で確認できます。

このとき外側のfor文は行を進め、内側のfor文は列を進めます。System.out.printで同じ行に値を並べ、行末でSystem.out.printlnを呼ぶと表の見た目に近い出力になると整理できます。

ループを使った二次元配列の操作

これらの代入をすべて手で書くと、行数や列数が増えたときに保守しにくくなります。そのため、実装パターンとしてよく見るのは、array.lengtharray[i].lengthを使って全要素を走査する書き方です。

int[][] array = new int[5][5];

for(int i = 0; i < array.length; i++) {
    for(int j = 0; j < array[i].length; j++) {
        if((i + j) % 2 == 0) {
            array[i][j] = 1;
        } else {
            array[i][j] = 0;
        }
    }
}

for(int i = 0; i < array.length; i++) {
    for(int j = 0; j < array[i].length; j++) {
        System.out.print(array[i][j] + " ");
    }
    System.out.println();
}

結果: 期待される出力は、行番号と列番号の合計が偶数の位置に1、奇数の位置に0が並ぶ形です。

1 0 1 0 1
0 1 0 1 0
1 0 1 0 1
0 1 0 1 0
1 0 1 0 1

結果: 期待される出力例は上記の通りで、%による偶奇判定が格子状のパターンとして表れます。

この例では、if文で条件を分けています。条件が単純な場合は三項演算子でも書けますが、学習段階では分岐の意図が読み取りやすい形にしておくほうが扱いやすくなると理解できます。

💡 Tips: 二次元配列の走査では、固定値の35よりもarray.lengtharray[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区切り文字の扱いテキスト出力
ファイル読込StringBufferedReader行単位で処理する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新規配列か変更かを明確にする変換処理
拡張forint[]for (int[] row : array)添字が必要な処理には不向き表示処理
ビット演算int>>色成分の位置を理解するRGB分解
リソース管理AutoCloseabletry-with-resourcesファイルを閉じる保存処理
ライブラリArrayUtilsclone依存関係を管理する配列操作
設計判断classprivate責務を分けるグラフクラス

二次元配列の応用例

二次元配列の基本が分かると、単なる表の保持だけでなく、変換、計算、保存、モデル化に使えるようになります。特に押さえたいのは、処理の対象が行単位なのか、列単位なのか、セル単位なのかを先に決めることです。

行列の加算

行列の加算では、同じ位置にある要素同士を足して新しい配列に入れます。そのため、matrixAmatrixBの行数、列数が一致していることが前提になると覚えるとよいでしょう。

public class MatrixAddition {
    public static void main(String[] args) {
        int[][] matrixA = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        int[][] matrixB = {
            {9, 8, 7},
            {6, 5, 4},
            {3, 2, 1}
        };

        int[][] resultMatrix = addMatrices(matrixA, matrixB);

        for (int[] row : resultMatrix) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
    }

    public static int[][] addMatrices(int[][] matrixA, int[][] matrixB) {
        int rows = matrixA.length;
        int cols = matrixA[0].length;
        int[][] resultMatrix = new int[rows][cols];

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                resultMatrix[i][j] = matrixA[i][j] + matrixB[i][j];
            }
        }

        return resultMatrix;
    }
}

結果: 期待される出力は、各セルが10になった3行3列の行列です。

10 10 10 
10 10 10 
10 10 10 

結果: 期待される出力例は上記の通りで、matrixA[i][j]matrixB[i][j]の和がresultMatrix[i][j]へ入ります。

この実装では、元の配列を書き換えずにresultMatrixを返しています。入力を壊さない設計にしておくと、同じ配列を別の処理にも渡しやすくなると考えられます。

二次元配列のトランスポーズ

トランスポーズは、行と列を入れ替える操作です。元の配列でoriginalArray[0][1]にあった値は、転置後にtransposedArray[1][0]へ移ると考えると整理しやすくなります。

int[][] originalArray = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

結果: 期待される状態は、3行3列のoriginalArray1から9までが行方向に並ぶ形です。

int[][] transposedArray = {
    {1, 4, 7},
    {2, 5, 8},
    {3, 6, 9}
};

結果: 期待される状態は、元の列が新しい行になったtransposedArrayが作られる形です。

int[][] transposedArray = new int[3][3];
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        transposedArray[i][j] = originalArray[j][i];
    }
}

結果: 期待される状態は、originalArray[j][i]の値がtransposedArray[i][j]へ移され、行と列が入れ替わる形です。

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        System.out.print(transposedArray[i][j] + " ");
    }
    System.out.println();
}

結果: 期待される出力は、転置後の配列を行ごとに表示する形です。

1 4 7 
2 5 8 
3 6 9 

結果: 期待される出力例は上記の通りで、元の縦方向の値が横方向に並びます。

ただし、行数と列数が違う配列ではnew int[cols][rows]のように転置後のサイズを入れ替えます。正方形だけを想定したnew int[3][3]のままでは、長方形のデータに対応できません。

ゲーム盤の作成

ゲーム盤は、セルごとに状態を持つ典型的な二次元配列の例です。0を空きマス、1を障害物のように決めると、数値だけで盤面の状態を表せますが、これは押さえたい点です。

import java.util.Random;

public class GameBoard {
    public static void main(String[] args) {
        int[][] board = new int[5][5];
        Random random = new Random();

        for(int i = 0; i < 5; i++) {
            for(int j = 0; j < 5; j++) {
                board[i][j] = random.nextInt(2); // 0 または 1 をランダムに配置
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }
}

結果: 期待される出力は、0または1が5行5列で並ぶ盤面です。Randomを使うため、具体的な値の並びは実行ごとに変わります。

1 0 0 1 0 
0 1 1 0 1 
1 0 1 0 1 
0 1 0 1 0 
1 1 0 0 0 

結果: 期待される出力例は上記のような形式です。ただし、乱数により同じ並びが再現されるとは限りません。

この構造は、迷路、ライフゲーム、簡易シミュレーションなどに応用できます。状態が増える場合は012のような数値だけでなく、enumやクラスを使う設計も候補になると言えるでしょう。

画像処理の基礎

画像は横方向のx座標と縦方向のy座標を持つため、二次元配列と相性があります。Java標準のBufferedImageではgetRGBでピクセル値を取り出し、setRGBで新しい値を書き戻せます。

import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class ImageProcessingExample {
    public static void main(String[] args) {
        try {
            // 画像を読み込む
            BufferedImage image = ImageIO.read(new File("path/to/your/image.jpg"));

            // 二次元配列を用いて画像データを格納する
            int width = image.getWidth();
            int height = image.getHeight();
            int[][] grayscaleData = new int[height][width];

            // ピクセルごとに色情報を取得し、グレースケール変換を行う
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    int rgb = image.getRGB(x, y);
                    int r = (rgb >> 16) & 0xFF;
                    int g = (rgb >> 8) & 0xFF;
                    int b = rgb & 0xFF;
                    int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
                    grayscaleData[y][x] = gray;
                }
            }

            // 新しい画像を作成し、グレースケールデータを適用する
            BufferedImage grayscaleImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    int gray = grayscaleData[y][x];
                    int rgb = (gray << 16) | (gray << 8) | gray;
                    grayscaleImage.setRGB(x, y, rgb);
                }
            }

            // グレースケール画像を保存する
            ImageIO.write(grayscaleImage, "jpg", new File("path/to/save/grayscale_image.jpg"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果: 期待される処理は、入力画像からグレースケール値を計算し、保存先パスに新しい画像を書き出す流れです。ファイルパスが存在しない場合や画像形式が読めない場合は例外処理に進みます。

このコードでは、grayscaleData[y][x]の順に値を保存しています。画像APIの座標はxyの順に渡す一方、配列は行を先に置くため、yを外側、xを内側にする対応関係を崩さないことが大切になるのが基本です。

⚠️ 注意: サンプル内のpath/to/your/image.jpgpath/to/save/grayscale_image.jpgは仮のパスです。実際のプロジェクトでは存在する入力ファイルと書き込み可能な保存先に置き換えてください。

データの保存と読み込み

表形式の文字列データは、String[][]として持つとファイルへの出力を組み立てやすくなります。簡単なテキスト保存では、行ごとに要素を区切り文字で連結し、最後に改行を入れる流れになります。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;

public class TwoDimensionalArrayExample {
    public static void main(String[] args) {
        // 二次元配列の宣言と初期化
        String[][] data = {
                {"Name", "Age", "City"},
                {"Alice", "30", "Tokyo"},
                {"Bob", "28", "Osaka"},
                {"Charlie", "25", "Nagoya"}
        };

        // ファイルにデータを保存
        try (FileWriter writer = new FileWriter(new File("data.txt"))) {
            for (String[] row : data) {
                for (String element : row) {
                    writer.write(element + ",");
                }
                writer.write("n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ファイルからデータを読み込み
        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果: 期待される出力は、data.txtへ書き込まれた各行がコンソールへ表示される形です。

Name,Age,City,
Alice,30,Tokyo,
Bob,28,Osaka,
Charlie,25,Nagoya,

結果: 期待される出力例は上記の通りで、各要素の後ろにカンマが付いた行として読み込まれます。

ただし、この書き方は簡易的な形式です。値の中にカンマや改行が入る可能性がある場合は、CSVライブラリを使う、引用符のエスケープ規則を実装するなど、入力データの性質に合わせた処理が必要になります。

Stream APIとの組み合わせ

配列の変換処理を短く書きたい場合、Arrays.streammapを組み合わせる方法があるのが目安です。一方、添字を細かく扱う処理では通常のfor文のほうが読みやすいこともあります。

int[][] numbers = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

結果: 期待される状態は、numbersに3行3列の整数が初期化される形です。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] numbers = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        int[][] doubledNumbers = Arrays.stream(numbers)
            .map(row -> Arrays.stream(row).map(n -> n * 2).toArray())
            .toArray(int[][]::new);

        // 結果の出力
        for (int[] row : doubledNumbers) {
            for (int n : row) {
                System.out.print(n + " ");
            }
            System.out.println();
        }
    }
}

結果: 期待される出力は、元の各要素を2倍にした二次元配列です。

2 4 6 
8 10 12 
14 16 18 

結果: 期待される出力例は上記の通りで、mapに渡したラムダ式n -> n * 2が全要素に適用されます。

この書き方では、外側のArrays.stream(numbers)が各行を処理し、内側のArrays.stream(row)が行内の値を処理します。小さな変換には読みやすい一方、デバッグ時に途中の添字を見たい場合はループのほうが追いやすくなるのがポイントです。

グラフの隣接行列

グラフ構造では、頂点同士が接続しているかを二次元配列で表せます。頂点0と頂点1がつながっているなら、無向グラフではadjacencyMatrix[0][1]adjacencyMatrix[1][0]の両方を1にします。

public class Graph {
    private int[][] adjacencyMatrix;

    public Graph(int n) {
        adjacencyMatrix = new int[n][n];
    }

    public void addEdge(int i, int j) {
        adjacencyMatrix[i][j] = 1;
        adjacencyMatrix[j][i] = 1;
    }

    public void removeEdge(int i, int j) {
        adjacencyMatrix[i][j] = 0;
        adjacencyMatrix[j][i] = 0;
    }

    public void display() {
        for (int[] row : adjacencyMatrix) {
            for (int cell : row) {
                System.out.print(cell + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph(5);
        graph.addEdge(0, 1);
        graph.addEdge(1, 2);
        graph.addEdge(2, 0);
        graph.addEdge(3, 4);
        graph.display();
    }
}

結果: 期待される出力は、5頂点の無向グラフを表す隣接行列です。

0 1 1 0 0 
1 0 1 0 0 
1 1 0 0 0 
0 0 0 0 1 
0 0 0 1 0 

結果: 期待される出力例は上記の通りで、頂点012が互いに接続され、頂点34が接続されています。

一方、頂点数が非常に多く、辺が少ないグラフでは、隣接行列よりも隣接リストのほうがメモリを抑えやすくなります。配列で表すか、List<List<Integer>>で表すかは、データの密度で判断するのが一般的です。

注意点と対処法

二次元配列の不具合は、添字の範囲、行配列のnull、サイズの思い込みから起きることが多くあります。一般的に、固定値でループを回すより、配列自身のlengthを参照するほうが変更に強い書き方になります。

メモリ管理と配列形状

Javaの二次元配列は、外側の配列と内側の配列に分かれているのが現実的です。そのため、new int[100][100]のような矩形配列だけでなく、new int[100][]として行ごとに長さを変える配列も作れます。

// 良い例:メモリ効率の高い矩形の二次元配列
int[][] goodExample = new int[100][100];

// 悪い例:メモリ効率の低い非矩形の二次元配列
int[][] badExample = new int[100][];
for (int i = 0; i < 100; i++) {
    badExample[i] = new int[i + 1];
}

結果: 期待される状態は、goodExampleが100行100列の矩形配列、badExampleが行ごとに列数の異なる配列になる形です。

この例のコメントでは非矩形配列を悪い例としていますが、常に避けるべき構造という意味ではありません。三角表のように行ごとの長さが自然に異なるデータでは、ジャグ配列が合う場合もあります。

ArrayIndexOutOfBoundsExceptionへの対策

ArrayIndexOutOfBoundsExceptionは、存在しない添字へアクセスしたときに発生します。特にi <= array.lengthのように等号を入れてしまうと、最後のループで範囲外になると整理できます。

int[][] array = new int[10][10];
for (int i = 0; i < array.length; i++) {
    for (int j = 0; j < array[i].length; j++) {
        array[i][j] = i * j;
    }
}

結果: 期待される状態は、10行10列の各要素に行番号と列番号の積が代入される形です。array.lengtharray[i].lengthを使うため、範囲外アクセスを避けやすくなります。

このとき、内側の条件にarray[0].lengthを使う書き方は、全行が同じ長さのときだけ成立します。行ごとに長さが異なる可能性があるなら、現在の行を示すarray[i].lengthを使うのが自然です。

NullPointerExceptionへの対策

NullPointerExceptionは、参照先がnullのままメンバーや要素へアクセスしたときに発生すると理解できます。new int[10][]のように外側だけ生成した配列では、各行はまだ生成されていません。

int[][] array = new int[10][];
if (array[0] != null) {
    array[0][0] = 1;  // このコードはNullPointerExceptionを発生させません
} else {
    System.out.println("The array row is null.");
}

結果: 期待される出力は、array[0]が生成されていないためThe array row is null.というメッセージです。

ただし、値を入れたい処理なら、判定するだけでなくarray[0] = new int[10]のように行配列を生成する必要があります。判定、生成、代入の順序を分けて考えると、例外の原因を追いやすくなります。

ℹ️ 補足: 例外を握りつぶす目的で広いcatch (Exception e)を置くより、入力値、添字、配列生成の前提をコード上で満たすほうが原因を特定しやすくなると覚えるとよいでしょう。

二次元配列のカスタマイズ方法

二次元配列はプリミティブ型だけでなく、参照型の要素も持てます。そのため、単なる数値表から、人物、座席、セル状態、設定値のような意味を持つデータ構造へ広げられます。

カスタムデータ型の使用

カスタムクラスを配列要素にすると、複数の値をひとまとまりに扱えますし、これが一つの目安です。たとえばPersonクラスにnameageを持たせると、二次元配列を座席表や名簿のように扱えます。

public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person[][] people = {
            {new Person("山田太郎", 25), new Person("佐藤花子", 30)},
            {new Person("田中一郎", 40), new Person("木村華子", 20)}
        };

        for (Person[] personRow : people) {
            for (Person person : personRow) {
                System.out.println("名前: " + person.name + ", 年齢: " + person.age);
            }
        }
    }
}

結果: 期待される出力は、4人分の名前と年齢が行ごとに表示される形です。

この設計では、配列が持つのはPersonオブジェクトへの参照です。同じオブジェクトを複数のセルから参照すると、一方で変更した内容が別のセルから見える場合があるため、共有してよいデータかを確認する必要があります。

外部ライブラリを利用した拡張

外部ライブラリを使うと、標準APIだけでは書きにくい配列操作を補えます。Apache Commons LangのArrayUtilsには配列を扱うメソッドがありますが、導入時はビルドツールの依存関係とライセンスを確認すると考えられます。

import org.apache.commons.lang3.ArrayUtils;

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

        // 二次元配列の深いコピーを作成
        int[][] copiedArray = ArrayUtils.clone(array);

        // clonedArrayの[0][0]の値を変更
        copiedArray[0][0] = 10;

        // original arrayとcloned arrayの要素を出力
        System.out.println("元の配列: ");
        for (int[] row : array) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }

        System.out.println("コピー後の配列: ");
        for (int[] row : copiedArray) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
    }
}

結果: 期待される出力は、元の配列とコピー後の配列が別々に表示され、コピー後の先頭要素だけが10になる形です。

もっとも、二次元配列のコピーでは浅いコピーと深いコピーの違いを確認する必要があります。プリミティブ配列では値の違いを確認しやすい一方、参照型配列では内側のオブジェクトまで複製されるかが設計上の焦点になります。

⚠️ 注意: 外部ライブラリのコードは、プロジェクトに依存関係を追加してからコンパイルすると言えるでしょう。Mavenならpom.xml、Gradleならbuild.gradleにApache Commons Langを追加してください。

使い分けると、単純な固定表はint[][]String[][]、行数や列数が動的に変わる表はList系、意味のあるセル情報はクラス化という判断になります。二次元配列だけに寄せるより、データの変化に合わせて構造を選ぶほうが読みやすいコードになります。

まとめ

Javaの二次元配列は、型[][] 変数名で宣言し、変数名[行][列]で値にアクセスする構造です。表形式のデータをそのまま表せるため、行列、ゲーム盤、画像処理、ファイル保存、グラフ表現まで幅広い場面で使えるのが基本です。

その理解で特に押さえたいのは、外側の配列が行を持ち、各行が内側の配列として存在する点です。この性質により、array.lengtharray[i].lengthを分けて使う必要があり、ジャグ配列では行ごとの長さを前提にしたループが求められます。

ただし、すべてのデータを二次元配列で表す必要はありません。要素の追加や削除が多いならArrayList、セルに複数の属性があるならclassrecord、グラフが疎なら隣接リストを検討すると、コードの意図が明確になります。

基本的には、固定サイズの表を高速に扱いたい場面では二次元配列が合いるのが目安です。一方、サイズ変更や複雑な検索が中心になる場面では、配列以外のデータ構造も候補に入れると設計の選択肢が広がります。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。