7.5 配列の配列
7.5.1 配列型の構成要素
配列によって直列的な情報をまとめて管理することができるようになりましたが、さらに情報が並列的な複雑さを持つ場合、これまでの配列では管理が難しくなります。行と列で構成される関係モデル(※1)情報を制御するプログラムを想像してください。情報は、直列的なものではなく、行と列でデータの場所が決定される多次元的な配置となります。もちろん、こうした情報を直列的な方法で管理することも可能ですが、直観的ではありません。
これを解決する手段として、配列の配列を生成する方法が考えられます。実は、配列の構成要素型に配列型を指定することも可能なのです。構成要素が配列方であるということは、すなわち配列の配列、配列の配列の配列などを生成することができるのです。
構成要素が配列型であることを表すためにはブラケットを複数個指定します。配列型変数の宣言では、ブラケットの数が入れ子になっている配列の深さを表しています。
int iaa[][]; //配列の配列 int iaaa[][][]; //配列の配列の配列
これらの変数には、配列型の構成要素を代入することができます。配列型を格納することができる配列を生成するには、配列生成式で、やはり [ ] を複数指定します。こうすることによって、配列型を格納することができる構成要素型を実現できるのです。
int iaa[][] = new int[要素数][]; //配列を格納できる配列 int iaaa[][][] = new int[要素数][][]; //配列の配列を格納できる配列
最初の [ ] には、配列のサイズを決定するための要素数を指定しなければなりません。しかし、iaa の各要素が参照する配列のサイズは干渉する必要がないので、ブラケットは空になります。iaa[0] 番は 3 つの要素を持つ数値型の配列を指すかもしれませんし、iaa[1] は 7 つの要素を持つ数値型の配列を指すかもしれません。配列の配列とは、配列型への参照を保有する配列に過ぎないので、参照先の配列サイズを固定する必要はないのです。
配列の配列にアクセスするには、iaa[ i ] と指定すれば配列が格納する配列を取得することができますし、配列が格納する配列の構成要素にアクセスするのであれば iaa[ i ] [ j ] と記述します。配列の配列の配列型であれば、iaaa[ i ] [ j ] [ k ] ... というようにたどることができます。
class Test { public static void main(String args[]) { int iArray1[] = { 1 , 10 }; int iArray2[] = { 100 , 1000 , 10000 }; int iaa[][] = new int[2][]; iaa[0] = iArray1; iaa[1] = iArray2; for(int i = 0 ; i < iaa.length ; i++) { for(int j = 0 ; j < iaa[i].length ; j++) { System.out.println("iaa[" + i + "][" + j + "] = " + iaa[i][j]); } } } }
>java Test iaa[0][0] = 1 iaa[0][1] = 10 iaa[1][0] = 100 iaa[1][1] = 1000 iaa[1][2] = 10000
コード1では、数値型配列を参照する配列 iaa を宣言しています。この配列の構成要素は 2 つで、それぞれの構成要素は何らかの数値型配列を参照することができます。iaa を宣言した時点では、各構成要素は null を参照しています。これに対し iaa[0] に数値型配列 iArray1 を、iaa[1] に iArray2 を代入しています。配列の構成要素が配列を参照しているというのは、この状態を表します。iaa の構成要素は数値型の配列を参照しているだけなので、その数値型の要素数には関与しません。iArray1 と iArray2 の要素数は異なるものですが、問題はありません。
次に、プログラムは for ループのネストによって、配列が格納する配列の要素をすべて画面に表示します。まず、トップレベルの配列である iaa 変数の要素数を iaa.length という文で調べています。次に、iaa の要素が格納する配列のサイズを調べ、これを順に表示しなければなりません。これは iaa[ i ].length という文で調べています。ループの最下層では iaa[ i ][ j ] という形で、配列の配列の要素を参照しています。実行結果を見れば、各要素が参照している配列を順に正しく表示していることが確認できます。
先ほどの配列生成式では、最初の [ ] 以外では要素数を省略しましたが、配列の配列の要素数を明示的に記述することで、宣言と同時に配列の配列もインスタンス化することができます。例えば、次のような宣言は有効です。
int iaa = new int[3][5];
このように宣言すれば、配列の配列も同時にインスタンス化され、それぞれ 5 つの要素を持つ数値型の配列となります。つまり、上記の文は次の文に等しいと考えることができます。
int iaa = new int[3][]; for(int i = 0 ; i < iaa.length ; i++) iaa[i] = new int[5];
このように、配列の配列の要素数を省略した場合は、各要素は null を参照しているため、個別に配列を代入して要素を初期化する必要があります。配列生成式ですべての要素数を指定することで、配列インスタンスが自動的に生成されることになります。ただし、これは配列の配列が固定的な要素数を保有する場合にのみ有効です。
class Test { public static void main(String args[]) { int iaa[][] = new int[2][2]; iaa[0][0] = 1; iaa[0][1] = 10; iaa[1][0] = 100; iaa[1][1] = 1000; System.out.println("iaa[0][0] = " + iaa[0][0]); System.out.println("iaa[0][1] = " + iaa[0][1]); System.out.println("iaa[1][0] = " + iaa[1][0]); System.out.println("iaa[1][1] = " + iaa[1][1]); } }
コード2の iaa 変数を初期化している配列生成式は、要素が格納する配列のサイズも明示的に指定しています。その結果、iaa の各要素が参照する配列のインスタンスも自動的に生成されます。プログラムでは明示的に配列の配列となるインスタンスを生成していませんが、正しくアクセスできることが実行結果からわかります。要素数を省略すれば、インスタンスが生成されていないので null 参照となり実行時例外が発生します。
7.5.2 配列の配列を初期化する
構成要素が配列型である配列の配列を配列初期化子で初期化するには、配列初期化子も配列の深さにあわせて入れ子にする必要があります。配列の配列を初期化する作業は面倒なので、宣言時に各配列の値が決定しているのであれば、配列初期化子を用いてインスタンス化することができます。配列のサイズは初期化子で指定された要素数に合わせて決定されます。
配列初期化子は、次のように入れ子にして指定することができます。
int iaa[][] = new int[][] { 配列初期化子1 , 配列初期化子2 , ... };
配列の配列の配列を初期化するのであれば、配列初期化子の配列初期化子の配列初期化子を指定します。複雑ですが、冷静に考えれば単純な入れ子構造なので難しいものではありません。配列生成式で初期化する場合も同じです。
int iaa[][] = new int[][] { 配列初期化子1 , 配列初期化子2 , ... };
型名の後に指定する [ ] の数を変数型に合わせる必要があります。その後は、やはり配列初期化子を指定するだけなので方法は同じです。
class Test { public static void main(String args[]) { int iaa[][] = { { 0 , 1 , 2 } , { 10 , 11 , 12 , 13 } , { 20 } , { 30 , 31} }; for(int i = 0 ; i < iaa.length ; i++) { for(int j = 0 ; j < iaa[i].length ; j++) { System.out.println("iaa[" + i + "][" + j + "] = " + iaa[i][j]); } } } }
>java Test iaa[0][0] = 0 iaa[0][1] = 1 iaa[0][2] = 2 iaa[1][0] = 10 iaa[1][1] = 11 iaa[1][2] = 12 iaa[1][3] = 13 iaa[2][0] = 20 iaa[3][0] = 30 iaa[3][1] = 31
コード3では、配列の配列を表す変数 iaa を初期化するために、入れ子の配列初期化子を指定しています。最初の配列初期化子 { 0 , 1 , 2 } は iaa[0] 要素が参照する配列を初期化しています。順に iaa[1] は { 10 , 11 , 12 , 13 } によって初期化されることになります。入れ子になった配列初期化子の数によって、最終的に配列の要素数が決定されます。実行結果を見れば、配列の配列が正しく初期化されていることが判ります。