Java 入門

Java 多次元配列

配列は、同じ型のデータの集合を格納するための強力なツールです。これまでは主に、単純なリストやデータの行のような「一次元配列」を扱ってきました。しかし、現実世界の多くのシナリオでは、テーブル、グリッド、あるいはさらに複雑な構造など、複数の次元を持つデータを表現する必要があります。

ここで登場するのが多次元配列です。多次元配列を使用すると、データを行と列(さらにはより複雑な階層)で整理できるため、構造化された情報のモデリングや操作が非常に容易になります。

1. 多次元配列を理解する

多次元配列は、本質的には「配列の配列」です。最も一般的な多次元配列は2次元(2D)配列で、これは行と列を持つグリッドやテーブルとしてイメージすると分かりやすいでしょう。

Javaは厳密には「真の多次元配列」を直接サポートしているわけではありませんが、各要素自体が別の配列であるような配列を作成することを許可しています。これにより、実質的に多次元構造をシミュレートしています。

スプレッドシートを想像してみてください。行と列があり、各セルにデータが保持されています。2次元配列もこれと同様に動作し、特定のデータにアクセスするためには「行インデックス」と「列インデックス」を指定する必要があります。

2. 2次元配列の宣言と初期化

2次元配列を宣言するには、2組の大括弧 [] を使用します。それぞれの組が1つの次元を表します。

2.1 宣言の構文

2次元配列を宣言する基本構文は以下の通りです。

データ型[][] 配列名;

例えば、整数型の2次元配列(行列)を宣言する場合は以下のようになります。

int[][] matrix;

2.2 配列サイズの初期化

2次元配列を初期化する際は、各次元の長さを指定する必要があります。2次元配列の場合、行数と列数を指定することを意味します。

// 3行4列の2次元配列を宣言して初期化
int[][] matrix = new int[3][4];

これにより、matrix という名前の2次元配列が作成され、すべての要素はデフォルト値(int の場合は 0、オブジェクトの場合は nullboolean の場合は false など)で初期化されます。

2.3 初期化リストの使用

一次元配列と同様に、宣言と同時に直接値を代入することもできます。内側の各中括弧 {} が1つの行を表します。

// 2次元配列を直接初期化
int[][] grades = {
    {90, 85, 92},  // 学生1の成績(例:数学、科学、英語)
    {78, 88, 80},  // 学生2の成績
    {95, 90, 87}   // 学生3の成績
};

この例では、grades は3行3列の配列になります。各行が1人の学生を表し、各列が特定の科目の成績を表しています。

3. ジャグ配列 (Jagged Arrays)

Javaの「配列の配列」という設計は、ジャグ配列(不規則配列とも呼ばれます)という強力な機能をもたらします。これは、内部配列(つまり各行)の長さが異なってもよいことを意味します。データの行ごとに長さがバラバラな場合に、この柔軟性が非常に役立ちます。

ジャグ配列を作成するには、外部配列を初期化する際に行数だけを指定し、その後で各行に対して個別に異なる長さを初期化します。

// 3行の2次元配列を宣言し、列数は後で設定する
int[][] irregularMatrix = new int[3][]; 

// 各行に異なる列数を初期化
irregularMatrix[0] = new int[5]; // 0行目は5列
irregularMatrix[1] = new int[2]; // 1行目は2列
irregularMatrix[2] = new int[4]; // 2行目は4列

この柔軟性が可能なのは、Javaにおいて2次元配列が実際には「参照を格納する配列」であり、各参照がそれぞれ異なるサイズの一次元配列を指しているためです。

4. 多次元配列の要素へのアクセス

2次元配列の特定の要素にアクセスするには、行インデックスと列インデックスの2つを指定します。一次元配列と同様に、インデックスは 0 から始まります。

構文は以下の通りです。

配列名[行インデックス][列インデックス]

先ほどの grades 配列を例に見てみましょう。

int[][] grades = {
    {90, 85, 92},
    {78, 88, 80},
    {95, 90, 87}
};

// 要素へのアクセス:
int firstStudentMathGrade = grades[0][0]; // 0行目0列目にアクセス(結果は 90)
System.out.println("1人目の学生の数学の成績: " + firstStudentMathGrade);

int secondStudentScienceGrade = grades[1][1]; // 1行目1列目にアクセス(結果は 88)
System.out.println("2人目の学生の科学の成績: " + secondStudentScienceGrade);

// 要素の修正:
grades[0][0] = 93; // 1人目の学生の数学の成績を 93 に書き換え
System.out.println("更新後の1人目の学生の数学の成績: " + grades[0][0]);

5. 2次元配列の length 属性を理解する

2次元配列において、.length 属性の振る舞いは少し特殊です。

  • 配列名.length: 行数(外部配列の長さ)を返します。
  • 配列名[行インデックス].length: その特定の行における列数(内部配列の長さ)を返します。

これは、ジャグ配列を処理する際に特に便利です。

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

System.out.println("行数: " + exampleArray.length);             // 出力: 3
System.out.println("0行目の長さ: " + exampleArray[0].length);    // 出力: 3
System.out.println("1行目の長さ: " + exampleArray[1].length);    // 出力: 2
System.out.println("2行目の長さ: " + exampleArray[2].length);    // 出力: 4

6. 実践ケーススタディ

6.1 ケース 1:簡易ゲームボードの格納

三目並べ(Tic-Tac-Toe)ゲームを作成する場合を考えてみましょう。3x3 のグリッドは、2次元配列の完璧な活用シーンです。文字(char)を使って空き、'X'、'O' を表現できます。

public class TicTacToeBoard {
    public static void main(String[] args) {
        // 3x3 の文字配列を三目並べのボードとして宣言・初期化
        char[][] board = {
            {' ', ' ', ' '}, // 0行目
            {' ', ' ', ' '}, // 1行目
            {' ', ' ', ' '}  // 2行目
        };

        // いくつか駒を置く
        board[0][0] = 'X'; // プレイヤーXが左上に置く
        board[1][1] = 'O'; // プレイヤーOが中央に置く
        board[0][1] = 'X'; // プレイヤーXが上部中央に置く

        // ボードの現在の状態を表示
        System.out.println("現在の三目並べボードの状態:");
        // 行をループ
        for (int i = 0; i < board.length; i++) {
            // 現在の行の列をループ
            for (int j = 0; j < board[i].length; j++) {
                System.out.print(board[i][j]); // 要素を表示
                if (j < board[i].length - 1) {
                    System.out.print(" | "); // 列の区切りを表示
                }
            }
            System.out.println(); // 改行
            if (i < board.length - 1) {
                System.out.println("---------"); // 行の区切りを表示
            }
        }
    }
}

6.2 ケース 2:売上データの格納

異なる製品の月次売上データを格納する必要があるとします。2次元配列なら簡単に対応できます。行を製品、列を月(または四半期)として表現します。

public class SalesData {
    public static void main(String[] args) {
        // 3つの製品の4つの四半期における売上データ
        // 行:製品 (製品A, 製品B, 製品C)
        // 列:四半期 (Q1, Q2, Q3, Q4)
        double[][] quarterlySales = {
            {1500.75, 1800.50, 2100.20, 1950.00}, // 製品Aの売上
            {1200.00, 1350.25, 1400.00, 1600.50}, // 製品Bの売上
            {800.00,  950.00,  1100.75, 1250.00}  // 製品Cの売上
        };

        // 製品Bの総売上を計算
        double totalSalesProductB = 0;
        for (int i = 0; i < quarterlySales[1].length; i++) { // 製品Bの行の各列をループ
            totalSalesProductB += quarterlySales[1][i];
        }
        System.out.println("製品Bの総売上: $" + totalSalesProductB);

        // すべての製品の第3四半期 (Q3) における総売上を計算
        double totalSalesQ3 = 0;
        for (int i = 0; i < quarterlySales.length; i++) { // 各行をループ
            totalSalesQ3 += quarterlySales[i][2]; // Q3のインデックスは 2
        }
        System.out.println("全製品のQ3総売上: $" + totalSalesQ3);

        // 製品Aの四半期最高売上を検索
        double highestSalesProductA = quarterlySales[0][0];
        for (int i = 1; i < quarterlySales[0].length; i++) {
            if (quarterlySales[0][i] > highestSalesProductA) {
                highestSalesProductA = quarterlySales[0][i];
            }
        }
        System.out.println("製品Aの最高四半期売上: $" + highestSalesProductA);
    }
}