やり直しJava

配列

Javaの配列は固定長で、Python・Ruby・Javascript等のように自動的にサイズは変わりません。可変長の要素を扱いたい場合はCollectionを用います

配列は参照型で 直接の親クラスはObjectであり、Objectクラスが持つメソッドを呼び出すこともできます。配列はnew演算子で生成します。配列として型定義した変数にはnullを代入することができます。

宣言

配列の宣言は型名の後ろに[]を付けます。変数の後ろに[]を付ける書き方もできますが、一般的には型名の後ろに[]を付ける記法が用いられます

データ型[]  変数;
データ型[][]  変数;
int[]  array;
double[][]  arrayList;

// 次のような書き方もできるが、推奨されない。
// int  array[];
// double  arrayList[][];

初期化

変数の宣言と同時に初期値を与えて変数を初期化することができます。

データ型[]  変数 = new データ型[配列の要素数];
データ型[]  変数 = new データ型[] { 初期値, 初期値, ... };
データ型[]  変数 = { 初期値, 初期値 , ... };
int[] array1 = new int[5];
int[] array2 = new int[] { 1, 2, 3 };
int[] array3 = {4, 5, 6 };

多次元配列の場合は {}(ブレース) をネストします。

int[][] twoDimensionArray = {
    { 11, 12, 13, },
    { 21, 22, 23, },
    { 31, 32, 33, },
};

宣言時に初期化されない場合は、配列の各要素はデフォルト値で初期化されます。各データ型ごとのデフォルト値は「変数とデータ型」の章の「プリミティブ型一覧」にまとめています。

int[] array = new int[5];        // 各要素は 0 で初期化される
String[] lines = new String[5];  // 各要素は null で初期化される

アクセス

配列要素にアクセスするには[](ブラケット)に要素のインデックスを指定します。インデックスは0オリジンです。範囲外のインデックスを指定した場合はArrayIndexOutOfBoundsExceptionが発生します。

int[] array = new int(/generics/);
array[0] = 5;
System.out.println(array[0]);  // 5
array[4] = 10;  // ArrayIndexOutOfBoundsException が発生する

配列の要素数はlengthで参照できます。

int[] array = new int[10];
System.out.println(array.length);  // 10

配列は共変

Java配列は共変です。共変については「総称型」の章の「変性」で説明します。簡単に説明すると、共変の場合、あるクラスSuperとSubがあり SubがSuperのサブタイプであるとすれば Sub[]はSuper[]のサブタイプとなります。これは一見直感的で便利なようにも思えますが、次の例のように型安全を損なう元凶となり得ます

Object[] objArray = new Integer[3];   // objArrayはObject[]と宣言されているが、実体はInteger[]
objArray[0] = "Hello World!";  // Integer[]にStringの要素を代入しようとしている。
                               // コンパイルは通るが 実行時にArrayStoreExceptionが発生する。

上のコードはコンパイラが型不一致のエラーを検出することができず、実行時に例外が発生します。この問題は 配列が共変であることに起因しています。

一方で 総称型は非変です。(総称型では共変の仕組みも提供していますが、型安全が保証されるように 制限が設けられています。)コンパイル時に型安全性を保証できるため、性能要件が許容できない場合を除くと 配列よりも総称型のCollectionを使う方が好ましいことが多いです。

配列の型と要素の型を混同しない

配列の共変に関連して、配列のキャストについて少し見てみます。

Object[] objArray = new String[] { "One", "Two" };  // 配列は共変なのでOK。
String[] strArray = (String[]) objArray;

objArrayはObject[]と宣言されていますが、実際にはString[]であるため String[]へのキャストは問題なく行えます。しかし、次のコードは実行時にClassCastExceptionが発生します。

Object[] objArray = new Object[] { "One", "Two" };
String[] strArray = (String[]) objArray;  // ClassCastException
String str = (String) objArray[0];  // これはOK。

objArrayの各要素はStringではありますが、入れ物がObject[]であるため String[]へキャストすることはできません。ただし、objArrayの要素はStringなので、個々の要素は問題なくStringにキャストすることができます。

当たり前の内容ではありますが、配列自体の型と配列の要素の型を混同しないよう注意が必要です

ユーティリティ

配列を扱うユーティリティとして java.util.Arraysクラスに各種クラスメソッドが用意されています。その中から便利なものをいくつか取り上げます。

ダンプ

配列の中身を出力したい場合に System.out.println()に配列を渡しても データ型を示すシグニチャとハッシュコードが表示されてしまいます。そのような場合には ArraysのtoString()クラスメソッドを使うと 配列の内容を出力することができます。

int[] array = {1, 2, 3 };
System.out.println(array);    // [I@311d617d
System.out.println(Arrays.toString(array));    // [1, 2, 3]

また、多次元配列の場合はdeepToString()クラスメソッドを使います。

int[][] twoDimensionArray = {
    { 11, 12, 13, },
    { 21, 22, 23, },
    { 31, 32, 33, },
};
System.out.println(Arrays.toString(twoDimensionArray));    // [[I@7c53a9eb, [I@ed17bee, [I@2a33fae0]
System.out.println(Arrays.deepToString(twoDimensionArray));  // [[11, 12, 13], [21, 22, 23], [31, 32, 33]]

全要素同士の比較

配列の全要素同士の比較を行いたい場合を考えます。比較演算子==を使うと同一インスタンスかどうかの判定を行ってしまい要素同士の比較には使えません。また、配列はObjectのサブクラスですがequals()メソッドはオーバーライドされていないため、equals()を呼び出しても 比較演算子==を使う場合と同じ結果になってしまいます

配列の全要素同士の比較を行う場合は Arrays.equals()クラスメソッドを使います。

int[] array1 = new int[] { 1, 2, 3, 4, 5 };
int[] array2 = new int[] { 1, 2, 3, 4, 5 };
System.out.println(array1 == array2);        // false:array1とarray2の参照先は別のオブジェクト
System.out.println(array1.equals(array2));    // false:比較演算子==を使う場合と同様
System.out.println(Arrays.equals(array1, array2));    // true:要素同士の比較を行う

多次元配列の要素同士の比較を行う場合は Arrays.deepEquals()クラスメソッドを使います。

int[][] arrayList1 = {
        { 11, 12, 13, },
        { 21, 22, 23, },
        { 31, 32, 33, },
};
int[][] arrayList2 = {
        { 11, 12, 13, },
        { 21, 22, 23, },
        { 31, 32, 33, },
};
System.out.println(arrayList1 == arrayList2);        // false:arrayList1とarrayList2の参照先は別のオブジェクト
System.out.println(arrayList1.equals(arrayList2));    // false:比較演算子==を使う場合と同様
System.out.println(Arrays.equals(arrayList1, arrayList2));        // false:個々の要素の同一性判定を行う
System.out.println(Arrays.deepEquals(arrayList1, arrayList2));    // true:要素同士の比較を行う