HOWTO · Java

Java のコンストラクターで配列を初期化する

この記事では、Java のコンストラクターで配列を初期化しようとしています。

このページの内容

Javaオブジェクトを作成するとき、配列フィールドの初期内容がコンストラクタ引数やほかの初期化処理によって決まるなら、コンストラクタで初期化します。Javaの既定値で問題なければ、フィールド用の配列を確保するだけで十分です。コンストラクタ内でローカル変数として宣言した配列を使えるのは、そのコンストラクタの実行中だけであり、オブジェクトの状態として保持されるわけではありません。

Javaのコンストラクタで配列フィールドを初期化する

配列をインスタンスフィールドとして宣言し、コンストラクタでそのフィールドに配列への参照を代入します。新しいオブジェクトが作られるたびにコンストラクタが実行されるため、そのオブジェクトの入力値を使ってフィールドを設定したり、オブジェクトごとに必要な値を計算したりできます。

配列フィールドと、そのフィールドが参照する配列は別のものです。フィールドが保持するのは参照で、要素を保持するのは配列です。new int[size] で配列を作成すると要素が確保され、それぞれに既定値が設定されます。this.values = values のようにローカル配列をフィールドに代入すると、その参照が保存されます。オブジェクトが独立した内容を持つ必要がある場合は、配列をコピーしてください。

Javaの配列で既定値を使う

Javaでは、コンストラクタ本体が実行される前にインスタンスフィールドへ既定値が設定されます。コンストラクタで配列を作成すると、要素にも既定値が設定されます。数値プリミティブ型の要素は0(整数型は0、floatとdoubleは0.0)、char型は\u0000、boolean型はfalse、Stringなどの参照型はnullで始まります。

たとえば、private int[] values = new int[3]; は、要素が3つとも0のインスタンスフィールドを作成します。代わりにprivate int[] values;と宣言し、コンストラクタ内でvalues = new int[3];を代入しても、各要素は0で始まります。その値を設定すること自体に別の意味がない限り、同じ値を書き込むループを追加する必要はありません。

コンストラクタで配列に特定の値を設定する

内容が固定なら配列初期化子を使い、オブジェクトごとに呼び出し側が内容を決めるならコンストラクタの引数で値を受け取ります。たとえば、this.values = new int[] {1, 2, 3}; とすれば、その3つの値がフィールドに設定されます。引数を受け取るコンストラクタでは、呼び出し側の配列が後から変更されたときにオブジェクトの内容にも影響させるかどうかに応じて、入力配列をそのまま代入するか、コピーして代入します。

次の例では、1つのクラスで複数の方法を示します。既定値で埋められたフィールドを作成し、渡された値をコピーし、別のフィールドを同じ値で埋め、インデックスから値を計算し、コンストラクタ内でローカル配列も使います。プログラムは各配列を表示するため、自分のクラスに合う部分を取り入れられます。

import java.util.Arrays;

public class ArraySetup {
    private final int[] defaults = new int[3];
    private final int[] supplied;
    private final int[] repeated = new int[3];
    private final int[] calculated = new int[3];

    public ArraySetup(int[] source) {
        supplied = Arrays.copyOf(source, source.length);
        Arrays.fill(repeated, 7);
        Arrays.setAll(calculated, index -> (index + 1) * 2);

        int[] local = {4, 5};
        System.out.println("Local: " + Arrays.toString(local));
    }

    public static void main(String[] args) {
        int[] input = {3, 6, 9};
        ArraySetup item = new ArraySetup(input);
        System.out.println("Defaults: " + Arrays.toString(item.defaults));
        System.out.println("Supplied: " + Arrays.toString(item.supplied));
        System.out.println("Repeated: " + Arrays.toString(item.repeated));
        System.out.println("Calculated: " + Arrays.toString(item.calculated));
    }
}

コンストラクタ内でローカル配列を作成する

コンストラクタ内のint[] local = {4, 5};のような宣言は、ローカル変数を作成します。コンストラクタ内のコードからは使えますが、コンストラクタが戻った後にほかのメソッドからアクセスすることはできません。作成したオブジェクトの状態として配列を保持したい場合は、たとえばthis.values = new int[] {4, 5};のように、代わりにインスタンスフィールドへ代入します。

ローカル配列は、フィールドを作る際の一時的な入力や、初期化中だけ必要な計算に使うと便利です。違いを明確にしておきましょう。フィールドや別のオブジェクトが配列への参照を保持すれば、配列オブジェクト自体は引き続き参照できます。しかし、コンストラクタ内のローカル変数だけでは、その配列は保持されません。

Arrays.copyOf()、Arrays.fill()、Arrays.setAll()を使う

内容の決まり方に応じてヘルパーを選びます。Arrays.copyOf(source, source.length)は、元の配列の要素を含む別の配列を作成します。指定する長さが元より短い場合も長い場合も、収まる要素がコピーされます。長くした場合に追加される要素には、その型の既定値が設定されます。オブジェクト配列では浅いコピーとなるため、参照はコピーされますが、参照先のオブジェクトまでは複製されません。

すべての要素に同じ値を設定するにはArrays.fill(array, value)を使います。各値をインデックスから計算するにはArrays.setAll(array, index -> expression)を使います。例では、fill処理によって配列全体に7を設定し、インデックスを使った関数によって連続する偶数を設定します。これらのメソッドは渡された配列の内容を更新するもので、置き換え用の配列は作成しません。

初期化方法を選ぶ

既定値を初期状態にしたい場合は、サイズを指定して配列を作り、その値を使います。少数の固定値には配列初期化子を、呼び出し側が内容を決める場合はコンストラクタ引数を使います。オブジェクトが独自の配列構造を持つ必要があれば入力配列をコピーし、同じ値を繰り返し設定するにはArrays.fill()を、インデックスから値を求めるにはArrays.setAll()を使います。コンストラクタの実行後もオブジェクトの状態として配列を使う場合は、インスタンスフィールドとして宣言してください。