Javaについて徹底解説!

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

大石 英人

開発エンジニア/Java20年/Java GOLD/リーダー/ボールド歴2年

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

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

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

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

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


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

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

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

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

int[] array = new int[3]; // 3つ分のintを格納できる配列を確保
array[0] = 12;
array[1] = 34;
array[2] = 56;

System.out.println(java.util.Arrays.toString(array)); // → [12, 34, 56]

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

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

int[] array = new int[3];
int index = 0;
array[index++] = 12; // 添え字を直に書かず、変数で表現する
array[index++] = 34;
array[index++] = 56;

System.out.println(java.util.Arrays.toString(array)); // → [12, 34, 56]

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

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

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

int[] array = new int[3]; // newした時点で全要素が0になっている
array[1] = 34; // 添え字1だけ34にする、他は0のまま

System.out.println(java.util.Arrays.toString(array)); // → [0, 34, 0]

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

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

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

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

int[] array = new int[3];

for (int i = 0; i < array.length; i++) {
	array[i] = i + 10; // 添え字に一律10を足した値で初期化しています
}

System.out.println(java.util.Arrays.toString(array)); // → [10, 11, 12]

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

// このサンプルを動かすのに必要なimport文
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

List<String> arraydata = Files.readAllLines(Paths.get("arraydata.txt")); // ファイルからデータを読み取る
int[] array = new int[arraydata.size()]; // ファイルの行数分の要素を持つ配列を生成する

for (int i = 0; i < arraydata.size(); i++) {
	array[i] = Integer.parseInt(arraydata.get(i)); // 行の内容をintに変換して配列へ設定
}

System.out.println(java.util.Arrays.toString(array)); // → [10, 20, 30]

arraydata.txtの内容:
10
20
30

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

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

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

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

int[] array = new int[3];

for (int i = 0; i < array.length; i++) {
	if ((i % 2) == 0) {
		array[i] = i + 10; // 偶数の添え字(0と2だけ)に一律10を足しています
	}
}

System.out.println(java.util.Arrays.toString(array)); // → [10, 0, 12]

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

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

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

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

int[] array = new int[3];

for (int a : array) {
	a = 10; // 拡張forループで配列の内容を全て10にしようとしているが、これでは駄目
}

System.out.println(java.util.Arrays.toString(array)); // → [0, 0, 0] 初期値(0)から変わっていません!!

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

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

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

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

int[] array = { 12, 34, 56 };
/* int[] array = new int[] { 12, 34, 56 }; // これでも意味は同じです。 */

System.out.println(java.util.Arrays.toString(array)); // → [12, 34, 56]

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

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

int[][] array = {{11, 12, 13}, {21, 22}};

System.out.println(java.util.Arrays.deepToString(array)); // → [[11, 12, 13], [21, 22]]

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

int[][][] array = {
	{
		{ 11, 12, 13 },
		{ 21, 22, 23 }
	},
	{
		{ 31, 32, 33 },
		{ 41, 42, 43 }
	}
};

System.out.println(java.util.Arrays.deepToString(array)); // → [[[11, 12, 13], [21, 22, 23]], [[31, 32, 33], [41, 42, 43]]]

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

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

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

int[] array = { 1, 2, 3 };

System.out.println(java.util.Arrays.toString(array)); // → [1, 2, 3]

array = new int[] { 12, 34, 56 }; // arrayの指す先の配列を新しく生成した配列インスタンスにする

System.out.println(java.util.Arrays.toString(array)); // → [12, 34, 56]

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

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

int[][] array1 = { { 11, 12, 13 }, { 21, 22 } };
int[][] array2 = new int[2][]; // 空の2次元配列を生成しただけなので、[null, null] の状態

array2[0] = array1[1]; // array2の添え字0が指す先を、array1の添え字1の配列にする

System.out.println(java.util.Arrays.deepToString(array2)); // → [[21, 22], null]

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

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

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

int[] array1 = { 1, 2, 3 };
int[] array2 = new int[array1.length]; // 全ての要素は初期値の0

for (int i = 0; i < array1.length; i++) {
	array2[i] = array1[i];
}

System.out.println(java.util.Arrays.toString(array2)); // → [1, 2, 3]

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

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

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

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

int[] array1 = { 1, 2, 3 };
int[] array2 = array1.clone();

System.out.println(java.util.Arrays.toString(array2)); // → [1, 2, 3]

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

String[] array1 = {"A", "B"};
String[] array2 = array1.clone();

System.out.println(java.util.Arrays.toString(array2)); // → [A, B]

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

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

クラス:

java.lang.System

メソッド:

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
		src - 転送元配列
		srcPos - ソース配列の開始位置
		dest - 転送先配列
		destPos - 転送先データ内の開始位置
		length - コピーされる配列要素の数

int[] array1 = { 1, 2, 3 };
int[] array2 = new int[array1.length]; // 長さはarray1と同じ、全ての要素は初期値の0
int[] array3 = new int[array1.length]; // 同上

System.arraycopy(array1, 0, array2, 0, array1.length); // array1の全要素(0から最後まで)をコピーする
System.arraycopy(array1, 0, array3, 0, 1); // array1の一部分(0から1まで)をコピーする
System.out.println(java.util.Arrays.toString(array2)); // → [1, 2, 3]
System.out.println(java.util.Arrays.toString(array3)); // → [1, 2, 0]

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

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

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

クラス:

java.util.Arrays

メソッド:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
public static byte[] copyOf(byte[] original, int newLength)
public static short[] copyOf(short[] original, int newLength)
public static int[] copyOf(int[] original, int newLength)
public static long[] copyOf(long[] original, int newLength)
public static char[] copyOf(char[] original, int newLength)
public static float[] copyOf(float[] original, int newLength)
public static double[] copyOf(double[] original, int newLength)
public static boolean[] copyOf(boolean[] original, int newLength)

引数の意味

  • original – 範囲のコピー先となる配列
  • newLength – 返されるコピーの長さ
int[] array1 = { 1, 2, 3 };
int[] array2 = Arrays.copyOf(array1, array1.length); // 同じ長さの配列を作り、全要素をコピー
int[] array3 = Arrays.copyOf(array1, 5); // 長さ5の配列を作り、追加要素はデフォルトの0
int[] array4 = Arrays.copyOf(array1, 2); // 長さ2の配列を作り、長さが足りない部分は除外

System.out.println(java.util.Arrays.toString(array2)); // → [1, 2, 3]
System.out.println(java.util.Arrays.toString(array3)); // → [1, 2, 3, 0, 0]
System.out.println(java.util.Arrays.toString(array4)); // → [1, 2]

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

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

クラス:

java.util.Arrays

メソッド:

public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)
public static byte[] copyOfRange(byte[] original, int from, int to)
public static short[] copyOfRange(short[] original, int from, int to)
public static int[] copyOfRange(int[] original, int from, int to)
public static long[] copyOfRange(long[] original, int from, int to)
public static char[] copyOfRange(char[] original, int from, int to)
public static float[] copyOfRange(float[] original, int from, int to)
public static double[] copyOfRange(double[] original, int from, int to)
public static boolean[] copyOfRange(boolean[] original, int from, int to)

引数の意味

  • original – 範囲のコピー先となる配列
  • from – コピーされる範囲の最初のインデックス(これを含む)
  • to – コピーされる範囲の最後のインデックス(これを含まない)。 (このインデックスは配列の外側に存在することもある。)
int[] array1 = { 1, 2, 3 };
int[] array2 = Arrays.copyOfRange(array1, 0, array1.length); // 同じ長さの配列を作り、全要素をコピー(copyOfと同じ動作)
int[] array3 = Arrays.copyOfRange(array1, 0, 5); // 配列の添え字1~3までコピーし、全体で長さ5の配列を作る
int[] array4 = Arrays.copyOfRange(array1, 1, 3); // 配列の添え字1~2までコピーする

System.out.println(java.util.Arrays.toString(array2)); // → [1, 2, 3]
System.out.println(java.util.Arrays.toString(array3)); // → [1, 2, 3, 0, 0]
System.out.println(java.util.Arrays.toString(array4)); // → [2, 3]

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

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

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

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

List<Integer> list = Arrays.asList(1, 2, 3);
Integer[] array = list.toArray(new Integer[list.size()]);

System.out.println(java.util.Arrays.toString(array)); // → [1, 2, 3]

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

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

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
Integer[] array = set.toArray(new Integer[set.size()]);

System.out.println(java.util.Arrays.toString(array)); // → [1, 2, 3]

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

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

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

Map<Integer, String> map = new HashMap<>();
map.put(1, "一");
map.put(2, "二");
map.put(3, "三");

Set<Integer> keySet = map.keySet(); // MapからキーをSetとして取り出す
Collection<String> values = map.values(); // Mapから値をCollectionとして取り出す
Integer[] array1 = keySet.toArray(new Integer[keySet.size()]); // Setを配列に変換する
String[] array2 = values.toArray(new String[values.size()]); // Collectionを配列に変換する

System.out.println(java.util.Arrays.toString(array1)); // → [1, 2, 3]
System.out.println(java.util.Arrays.toString(array2)); // → [一, 二, 三]

5-4.Stream.toArrayを使う

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

List<Integer> list = Arrays.asList(1, 2, 3);
Integer[] array = list.stream().map(i -> i + 10).toArray(Integer[]::new); // Listの各要素に10を足し、結果をIntegerの配列として取り出す

System.out.println(java.util.Arrays.toString(array)); // → [11, 12, 13]

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

 

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


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

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

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

クラス:

java.util.Arrays

メソッド:

public static void fill(Object[] a, Object val)
public static void fill(boolean[] a, boolean val)
public static void fill(byte[] a, byte val)
public static void fill(char[] a, char val)
public static void fill(double[] a, double val)
public static void fill(float[] a, float val)
public static void fill(int[] a, int val)
public static void fill(long[] a, long val)
public static void fill(short[] a, short val)

引数の意味

  • a – 値を代入する配列
  • val – 配列のすべての要素に格納する値
int[] array = new int[3];
Arrays.fill(array, 10);

System.out.println(java.util.Arrays.toString(array)); // → [10, 10, 10]

Object[] arrayObj = new Object[3];
Arrays.fill(arrayObj, new Object());

System.out.println(java.util.Arrays.toString(arrayObj)); // → [java.lang.Object@67b64c45, java.lang.Object@67b64c45, java.lang.Object@67b64c45] ※全て同じObjectのインスタンスを指している

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

クラス:

java.util.Arrays

メソッド:

public static void fill(Object[] a, int fromIndex, int toIndex, Object val)
public static void fill(boolean[] a, int fromIndex, int toIndex, boolean val)
public static void fill(byte[] a, int fromIndex, int toIndex, byte val)
public static void fill(char[] a, int fromIndex, int toIndex, char val)
public static void fill(double[] a, int fromIndex, int toIndex, double val)
public static void fill(float[] a, int fromIndex, int toIndex, float val)
public static void fill(int[] a, int fromIndex, int toIndex, int val)
public static void fill(long[] a, int fromIndex, int toIndex, long val)
public static void fill(short[] a, int fromIndex, int toIndex, short val)

引数の意味

  • a – 値を代入する配列
  • fromIndex – 指定された値を代入する最初の要素(これを含む)のインデックス
  • toIndex – 指定された値を代入する最後の要素(これを含まない)のインデックス
  • val – 配列のすべての要素に格納する値
int[] array = new int[3];
Arrays.fill(array, 1, 3, 10); // 添え字1~2にだけ10を設定する

System.out.println(java.util.Arrays.toString(array)); // → [0, 10, 10]

7.さいごに

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

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

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

私たちは、全てのエンジニアに市場価値を高め自身の望む理想のキャリアを歩んでいただきたいと考えています。もし、今あなたが転職を検討しているのであればこちらの記事をご一読ください。理想のキャリアを実現するためのヒントが見つかるはずです。

『技術力』と『人間力』を高め市場価値の高いエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって遙かに高い水準の成果を出し、関わる全ての人々に感動を与え続ける集団でありたいと考えています。

高い水準で仕事を進めていただくためにも、弊社では次のような環境を用意しています。

  • 定年までIT業界で働くためのスキル(技術力、人間力)が身につく支援
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な人事評価制度
  • 平均残業時間17時間!毎週の稼動確認を徹底しているから実現できる働きやすい環境

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。

コメント

文系IT未経験歓迎!【23卒対象】新卒採用のエントリーを受付中!
文系IT未経験歓迎!
【23卒対象】新卒採用のエントリーを受付中!