Javaについて徹底解説!

Java配列の初期化大辞典! 配列の初期化の全てがここにある!

プログラミングでのデータ構造の基本は配列です。配列を使う際は、何かしらの初期値を設定したいですよね。また、何か別のデータから配列を作りたいこともあるでしょう。

この配列の初期化には、大きく分けて以下の方法があります(Java 10の時点)。この記事では、それぞれの方法についてサンプルを交えてお伝えします。

  1. 配列の各要素に添え字を介して値を設定する
  2. 配列をループさせて、配列の各要素に値を設定する
  3. 配列の変数に、配列の内容を表現したブロックを代入する
  4. 他の配列から内容をコピーする
  5. コレクション(ListSetMap)Streamから配列を生成する
  6. 固定の値で設定する(Arrays.fill)

なお、この記事では「配列の初期化」を「配列を使い始める前に行う、配列への値の設定方法」としています。厳密には配列の宣言・生成時に初期値を設定することと、生成した配列に値を設定することは別のことですが、広い意味での初期化として扱います。

本記事では、分かりやすさ重視でintの配列とJavaの標準APIだけを使います。int以外のプリミティブ型や参照型の配列でも大体同じです。多次元配列の初期化でも大体同じですが、少しややこしくなりますので、各節に多次元配列版を個別に記載しているところもあります。

1.配列の各要素に添え字を介して値を設定する

1-1.配列の要素ごとに値を設定する

最も基本となる方法で、配列の添え字(インデックスとも言います)ごとに、値を明示的に設定する方法です。

配列の添え字の数が少ない、あるいは固定ならこの方法でも十分です。Javaでは、配列の添え字は0から始まることに注意しましょう(他のプログラミング言語では、1から始まるものもあるのです!!)

いつも手抜きをすることを考えるプログラマーが良く使うのは、添え字を変数にすることです。

例えば以下のようにすると、順番を入れ替えたい時でも行単位でカット&ペーストするだけなので楽ちんです。他にも、例えばJDBCResultSetから値を取得する時にも、同じようなことをしたりしますね。

1-2.配列の初期値を活用して楽をする

Javaの配列の各要素には、配列の生成時に初期値が必ず設定されます。ルールはクラスのフィールドの初期値と同じで、intなどの数値型プリミティブなら0booleanならfalseStringなどの参照型ならnullです。

ですから、それぞれの型の初期値で良い要素は、明示的に値を設定しなくても良いのです。初期値を上手く使って、なるべく楽をしましょう。

2.配列をループさせて、配列の各要素に値を設定する

2-1.全ての要素に設定する場合

これも基本的な方法です。配列の要素数が大きい場合、あるいは特定の要素だけ条件付けて行いたい場合、添え字の数字から考えられる何かのルールがある場合は、この方法で行うのが便利でしょう。

値を設定したい範囲が配列のすべてではないなら、for文でのインデックスの初期値、終了条件、インデックスの加算処理を変えましょう。

これを応用すると、何かのファイルに値を書き込んでおいて、それを配列に一度に設定できたりします。例えば以下のような感じで、ファイルの行ごとに記述した数値を配列へ設定できたりします。

この応用先はファイルからの読み出しに限らず、データベースからの読み出しや、ネットワークを経由した読み出しなど、様々です。1行の内容も1つの値だけではなくCSVとするなどすれば、もっと多くの情報を詰め込めます。なるべくプログラム上では楽をしたいですね。

2-2.一部の要素に設定する場合

配列の添え字でループをしながら、特定の要素にだけ値を設定したいなら、ループの途中でif文で判定すると良いでしょう。

ここでは例として、偶数の添え字の要素にだけ値を設定してみます。ちなみに、このif文の条件は偶数・奇数判定のイディオムなので、ついでに覚えておきましょう。奇数の場合は、比較する値が1になります。

2-3.NG例:拡張forループ

拡張forループによるループの中からは、配列の要素へは値を設定できないことに注意しましょう。

なぜかと言うと、ループごとに使用できる変数は、プリミティブ型の場合は各要素のコピーされた値、参照型の場合は各要素のインスタンスを指すローカル変数でしかないからです。

配列の値を読み出すだけなら拡張forループは便利ですが、配列に値を設定するには、配列を指す変数への添え字を介したアクセスが基本となります。

3.配列の変数に、配列の内容を表現したブロックを代入する

3-1.配列宣言時に固定値を設定する

配列に固定された値を設定すればいいのなら、これがお勧めです。{}で囲った範囲(これをJavaではブロックと呼びます)が配列のインスタンスを戻すのだと考えてください。

これだと配列の要素数を記述しなくてもいいので、とても楽ちんですね!! ただし、配列の全要素の値を記述しなければならないので、値の数が多いと大変です。その場合は前述したループやファイルからの読み込みを考えてみましょう。

多次元配列の場合は以下のようになります。最も高い次元の配列のブロックの中に、次の次元の配列のブロックを埋め込むという感じです。次元が増えれば増えるほど、内部に埋め込むブロックが増えていきます。

この例では2次元配列です。2次元配列の1番目(array[0])は要素が3つの配列、2番目(array[1])は要素が2つの配列です。

3次元配列の場合は以下のようになります。{}が一つの配列で、それが入れ子になっているという構造は変わりません。

配列の次元が多くなると、インデントすればまだ読めますが、それでもご覧の通りどんどんややこしくなります。ですから、次元が大きい場合はプログラムで初期化をした方が分かりやすくなるでしょう。

3-2.配列の変数が指す先を後から変更する

別に宣言した配列に別の配列を再代入できますので、これを初期化の代わりにしても良いでしょう。要は、新しい配列をもう一つ生成し、配列の変数が指す先を新しい配列に変更しているということです。

配列は参照型の変数です。ですから、配列の変数が指す先の実体である配列インスタンスとでも呼ぶべきものを、いつでも変更できるのです。

多次元配列も同じ考えで、例えば2次元配列の1次元目は、別の配列のインスタンスを指しているだけなのです。

4.違う配列から内容をコピーする

4-1.ループで二つの配列をコピーする

時には、他の配列の内容をコピーして初期化としたい場合もあるでしょう。その場合の基本形は、ループでコピー元・先の配列の値をコピーすることです。

intなどのプリミティブ型の配列ならばこれで問題はありません。コピー先とコピー元の配列で持っている値は別々のものになるので、片方の配列の設定値の変更は、お互いの配列へ影響を及ぼすことはありません。

ですが、参照型の配列は要注意です。参照先のインスタンスがコピー元とコピー先の配列で同じになるからです。詳細な説明は省きますが、興味がある方はシャローコピー(shallow copy)とディープコピー(deep copy)で調べてみると良いでしょう。

4-2.Object.cloneでコピーする

自分で配列をループをしてもいいですが、単純な値のコピーをするだけならcloneをしてもいいでしょう。

参照型の配列の場合も同様です。新しい配列の各要素は、元の配列が指していたインスタンスを指しているだけということには気を付けましょう。二つの配列で同じインスタンスを共有しているということです。

4-3.System.arraycopyでコピーする

cloneでは全体がコピーされます。場合によっては配列の一部だけをコピーしたいこともあるでしょう。その時に使えるのは、まずはSystem.arraycopyです。

クラス:

メソッド:

4-4.Arrays.copyOfでコピーする

System.arraycopyでは、コピー元・先の二つの配列をあらかじめ用意する必要がありました。Arrays.copyOfでは、System.arraycopyと似たような動きをしますが、コピー元の配列だけがあればOKです。

しかも、不要な部分があればそれを除外することもできます。ですが、Arrays.copyOfの場合はコピーの開始位置が0番目からなので注意しましょう。

クラス:

メソッド:

引数の意味

  • original – 範囲のコピー先となる配列
  • newLength – 返されるコピーの長さ

4-3.Arrays.copyOfRangeでコピーする

Arrays.copyOfは便利なものでしたが、コピーの範囲を変えたければArrays.copyOfRangeの方が良いでしょう。

クラス:

メソッド:

引数の意味

  • original – 範囲のコピー先となる配列
  • from – コピーされる範囲の最初のインデックス(これを含む)
  • to – コピーされる範囲の最後のインデックス(これを含まない)。 (このインデックスは配列の外側に存在することもある。)

5.コレクション(ListSetMap)などから配列を生成する

コレクションとしてよく使うのはListSetMapだと思います。ここでは、これらのコレクションから配列としてデータを取り出す方法をお伝えします。

5-1.Collection.toArrayを使う(List)

Listに格納されている内容を配列として参照したいこともあるでしょう。もちろん、簡単にできます。例えば、以下のような感じです。この場合は、List内での格納順序と、配列内の添え字の順序は同じになります。

5-2.Collection.toArrayを使う方法(Set)

コレクションとして値を入れている型がSetでもやり方は同じで、Listと同じようにtoArray()を使うだけです。

ただし、配列にどの順番で入るかは要注意です。実体がHashSetなら明確な順序の規則はありません(どう並ぶか分からない)TreeSetLinkedHashSetなどの他のSetでは決められたルールがありますので、詳細は各クラスのAPI仕様を確認しましょう。

5-3.Collection.toArrayを使う(Map)

Collection.toArrayは、Mapに格納されているキーや値を配列として取り出したい場合にも使えます。考え方としてはListとSetの場合と同じで、Mapのキーや値をSetとCollectionとして取り出し、それを配列に変換します。

5-4.Stream.toArrayを使う

Java 8から使えるようになったStream APIを使っても同じようなことができます。

Streamを使っているとCollectionが中心となるので配列を使うケースは少なくなります。でも、このようにStreamで処理をした結果を配列として取り出すこともできるので、覚えておいても損はありませんね。

 

なお、このサンプルは参照型のIntegerを使っていますが、プリミティブ型であればいいなら、IntStreamなどを使うともっとお手軽に書けたりします。

6.Arrays.fillで固定値を設定する

今までの方法では、設定したい値やコピー元となる配列、値を持っているコレクションが既にある場合についてお伝えしてきました。ですが、実際には単純に同じ値で初期化ができさえすれば用が済む場合もあります。

その場合はArrays.fillを使うと、同じ値を一度に設定できます。大変便利ですね。ただし、Objectを使う場合は全ての要素が同じオブジェクトを指すことになりますので、使い道には気を付けてください。

クラス:

メソッド:

引数の意味

  • a – 値を代入する配列
  • val – 配列のすべての要素に格納する値

Arrays.fillは、配列の一部の範囲だけに設定できるオーバーロードされたメソッドが存在します。開始位置と、終了位置の一つ後を指定します。

クラス:

メソッド:

引数の意味

  • a – 値を代入する配列
  • fromIndex – 指定された値を代入する最初の要素(これを含む)のインデックス
  • toIndex – 指定された値を代入する最後の要素(これを含まない)のインデックス
  • val – 配列のすべての要素に格納する値

7.さいごに

この記事では配列を初期化するというよりも、何かを配列に変換するという内容が多くなりました。

配列はほとんどすべてのプログラミング言語で存在しますので、同じようなやり方、あるいは異なったやり方が他のプログラミング言語にもあるはずです。

このように、配列の初期化・変換の仕方には色々なやり方がありますので、状況に応じて適材適所で使えるようになりましょう!!