このようなお悩みがあるのではないでしょうか。Javaの「配列」は、同じデータ型の変数をまとめて処理したいときに便利です。アプリ開発で配列を活用すれば、大量のデータを扱う処理が効率的に行えるようになります。
しかし、Javaの配列にはさまざまなルールがあり、通常の変数とは異なる点もあります。配列を正しく扱わなければ、プログラムが意図しない動作をする原因になるので注意が必要です。
本記事では、Javaの配列の使い方や注意点、アプリ開発に役立つ応用法などについて解説します。この記事を読めば、Javaの配列を正しく扱えるようになり、アプリを効率的に開発できるようになるでしょう。
目次
Javaの「配列」とは、同じデータ型の変数をまとめて管理するための変数です。配列にはデータが連続して並び、格納されたデータには「0」から順番に「番号(インデックス)」が割り振られます。
全体像を把握するために、通常の変数と配列を比較してみましょう。Javaの配列の書き方や使い方については後述します。ここでは、まず配列のプログラムの雰囲気をつかんでみてください。
<5つの整数を通常の変数で扱う場合>
// int型の変数を宣言する
int number1;
int number2;
int number3;
int number4;
int number5;
// 各変数に数値を格納する
number1 = 10;
number2 = 30;
number3 = 50;
number4 = 40;
number5 = 20;
<5つの整数を配列で扱う場合>
// int型の配列を宣言する
int[] numbers;
// int型の配列を生成する
numbers = new int[5];
// 各変数に数値を格納する
numbers[0] = 10;
numbers[1] = 30;
numbers[2] = 50;
numbers[3] = 40;
numbers[4] = 20;
通常の変数の場合は、int型の変数を5つ宣言しています。しかし、配列を使う場合は「numbers」というひとつの変数だけで、5つの整数値を扱えることがポイントです。この点を踏まえて、Javaの配列についてさらに詳しく見ていきましょう。
Javaのプログラミングで配列を使うメリットは下記2つです。本章では、それぞれのメリットについて詳しく解説します。
配列を使う一番のメリットは、一度に大量のデータを扱えることです。配列には、同じデータ型の変数をまとめて格納できます。たとえば、1万個の整数値を扱うプログラムを書くとき、個別の変数で扱うと下記のように1万個の変数が必要です。
int number1, number2, number3… (中略), number9999, number10000;
気の遠くなるほど手間のかかる作業ですよね。画面上に数値を表示したいときも、ひとつずつprintlnメソッドで出力していかないといけません。一方で、配列を使えば下記のようにごく短いコードで、1万個の整数値が入る変数を用意できます。
int[] numbers = new int[10000];
配列を使うとソースコードが簡潔になるため、コーディングのミスが減ります。また、大量のデータをひとつの変数だけで処理できるので、プログラミングの効率が大幅に高まることも魅力です。
Javaで配列を使うと、配列関連の便利なライブラリやメソッドが使えるようになります。たとえば、後述する「java.util.Arrays」に含まれている、データの検索や並び替えなどのメソッドです。
大量のデータを扱うときは、さまざまな処理が必要になります。「数万件のデータから特定の情報を検出したい」というのはよくあるケースです。しかし、個別の変数にデータが分かれていると、必要なメソッドが使えないので作業できません。配列にまとめることで、初めて大量のデータを処理できるようになります。
「データを効率的に取り扱うためには配列が必須」といっても過言ではありません。なお、配列関連のメソッドの使い方については、後ほど詳しく解説するのでぜひ参考にしてみてください。
Javaの配列を使う際は、プログラミングの基本的なルールを守る必要があります。本章では、特に重要な下記2つのポイントを解説します。
Javaの配列には複数の値が格納されており、それぞれの値を「要素」と呼びます。配列そのものは配列の変数名で扱いますが、個別の要素を扱いたい場合は下記のように「インデックス」でアクセスする必要があります。
配列の変数名[インデックス]
インデックスを使うことにより、個々の値を設定したり参照したりすることが可能です。
ただし、インデックスは「1」ではなく「0」から始まることに注意してください。たとえば、10の要素から構成される配列の場合、1番目のインデックスは「0」で最後の10番目は「9」になります。
numbers[0] = 10; // numbers配列の1番目を「10」を格納する
numbers[9] = 100; // numbers配列の10番目に「100」を格納する
インデックスの使い方を誤ると、Javaのプログラムが正常に動作しなくなることがあるので注意が必要です。最初は間違えることも多いですが、少しずつ慣れていきましょう。
Javaの配列には「同じデータ型」しか格納できません。配列も通常の変数と同じように、宣言時にデータ型を指定する必要があります。異なる型のデータを配列に格納しようとすると、エラーが出てプログラムを実行できません。
int[] numbers = new int[10]; // int型の配列を生成する
numbers[0] = 100; // 配列と同じint型の格納なのでOK
numbers[1] = 10.0; // 配列と違うdouble型の格納なのでNG
numbers[2] = “文字列”; // 配列と違う文字列型の格納なのでNG
配列のデータ型と格納する値のデータ型は、同じでなければなりません。別のデータ型の値を配列に格納する必要がある場合は、下記のようにキャスト(型変換)を行いましょう。
numbers[1] = (int)10.0; // double型をint型に変換してから格納する
ただし、文字列は「基本データ型」ではないので、文字列をint型やdouble型などに変換することは基本的に不可能です。また、浮動小数点を整数にキャストすると、小数点以下の情報が失われてデータの精度が低下します。
本章では、Javaの配列の基本的な書き方や使い方について、下記5つのステップごとに詳しく解説します。どれも非常に重要なことなので、ひとつずつ丁寧に見ていきましょう。
Javaの配列を使うためには、まずは通常の変数と同じように「宣言」を行う必要があります。配列の宣言は「型名[] 配列名;」という構文で行いましょう。
int[] numbers; // int型の配列を宣言する
「[]」は「この変数は配列です」と示すためのものです。なお、「int numbers[];」のように「[]」を後ろに置く書き方もありますが、基本的には「[]」を型名の後ろに置くことを推奨します。こうすることで「int型の配列」だということが明確になるからです。
ただし、通常の変数とは異なり、配列は宣言するだけでは使えません。次のステップで配列を生成してあげる必要があります。
配列の宣言後は、配列の「生成」と「要素数」の設定が必要です。要素数とは配列のサイズ、つまり「配列にいくつのデータを格納できるか」を示すためのもの。これらの処理は「配列名 = new 型名[要素数];」という構文で同時に行います。
int[] numbers; // int型の配列を宣言する
numbers = new int[5]; // 要素数「5」の配列を生成する
ここで登場する「new演算子」は、「配列を生成する」ためのものです。ただし、要素数が分からなければ配列を生成できないため、配列の生成時には要素数の指定も必須です。
最初のステップで配列を宣言したあとに、new演算子による生成と要素数の指定を行うことで、ようやく配列が使えるようになります。重要なポイントなので必ず覚えておきましょう。
配列の要素数を決めたあとは、配列の「初期化」を行いましょう。なお、Javaでは配列を生成した段階で自動的にすべて「0」で初期化されるため、0にしておきたい場合はこのステップを飛ばしても構いません。
前述したように、Javaの配列はインデックスを使用して各要素にアクセスできます。あとは通常の変数と同じように、必要に応じて各要素に値を格納するだけです。
int[] numbers; // int型の配列を宣言する
numbers = new int[5]; // 要素数「5」の配列を生成する
numbers[0] = 10; // 1番目の要素に「10」を格納する
numbers[1] = 30; // 2番目の要素に「30」を格納する
numbers[2] = 50; // 3番目の要素に「50」を格納する
numbers[3] = 20; // 4番目の要素に「20」を格納する
numbers[4] = 40; // 5番目の要素に「40」を格納する
なお、配列を宣言したときのデータ型と、格納する値のデータ型は一致している必要があるので注意してください。
配列の初期化が完了したら、必要に応じて配列を活用してみましょう。今回は、配列の生成と初期化を行った後で、画面上に配列の各要素の中身を表示するプログラムを紹介します。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
int[] numbers; // int型の配列を宣言する
numbers = new int[5]; // 要素数「5」の配列を生成する
numbers[0] = 10; // 1番目の要素に「10」を格納する
numbers[1] = 30; // 2番目の要素に「30」を格納する
numbers[2] = 50; // 3番目の要素に「50」を格納する
numbers[3] = 20; // 4番目の要素に「20」を格納する
numbers[4] = 40; // 5番目の要素に「40」を格納する
// 配列のすべての要素を画面上に表示する
for(int i=0; i<5; i++) {
System.out.println(numbers[i]);
}
}
}
<実行結果>
10
30
50
20
40
上記のサンプルプログラムの要素数を増やしたり、配列のデータ型や格納する値を変更したりしてみましょう。自分でプログラムを試行錯誤しながら変えていくことで、学んだことが「知識」として身についていきます。
Javaの配列は、宣言と初期化を同時に行うことも可能です。この場合は下記のように、「型名[] 配列名 = {各要素};」という構文で書きましょう。
int[] numbers = {0, 1, 2, 3, 4}; // int型配列の宣言と生成、初期化を同時に行う
上記のように宣言と初期化を同時に行う場合は、「new int[5];」のようにnew演算子を使う必要はありません。初期化時に各要素を指定した時点で、プログラム内でnew演算子が自動的に動いて配列が生成されるからです。
なお、配列の「宣言」と「生成」を分けて書くのが面倒だという場合は、下記のようにひとつにまとめて書いても構いません。
int[] numbers = new int[5]; // int型配列の宣言と生成を同時に行う
配列をすぐに初期化する必要がない場合は、宣言と生成を同時に行う方が簡潔で分かりやすいためおすすめです。初期化の部分が短い場合は、そちらもまとめて記述するといいでしょう。
Javaの配列の基本的な書き方を確認したところで、応用的な書き方についても学んでいきたいですね。実際にアプリを開発するときに欠かせない知識なので、ぜひ下記6つのテクニックを参考にしてみてください。
配列の要素数を取得したいときは、下記のように「length」というメンバ変数を使いましょう。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
int[] numbers = {0, 9, 1, 8, 2, 7}; // 配列を生成する
System.out.println(numbers.length); // 配列の要素数を表示する
}
}
<実行結果>
6
lengthは配列を生成した時点で自動的に追加されるメンバ変数なので、必要に応じて呼び出すだけで配列の要素数を確認できます。
配列を処理するときは、要素数が必要になるケースが多々あります。たとえば、配列の要素数分だけ何らかの処理を行うときは、forループの回数指定に要素数の情報が必要です。配列のlengthメンバ変数はあらゆる場面で使用するので、必ず覚えておきましょう。
Javaの配列は生成後に要素数を変更できないため、新たな要素を追加することはできません。配列に要素を追加したい場合は、System.arraycopyメソッドを使用しましょう。System.arraycopyメソッドは下記の構文で使用します。
System.arraycopy(コピー元配列, 0, コピー先配列, 0, コピー元配列の要素数);
2番目と4番目の引数が「0」になっていますが、これはコピーする要素数の位置を指定するためのものです。今回はそのままコピーするため気にする必要はありません。System.arraycopyメソッドを使って、配列に新たな要素を追加するサンプルプログラムは下記のとおりです。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
int[] numbers1 = {0, 9, 1, 8, 2, 7}; // コピー元の配列を生成する
int[] numbers2 = new int[10]; // コピー先の配列を生成する
//配列をコピーする
System.arraycopy(numbers1, 0, numbers2, 0, numbers1.length);
// コピー後の配列の要素を表示する
for(int i=0; i<numbers2.length; i++) {
System.out.println(numbers2[i]);
}
}
}
<実行結果>
0
9
1
8
2
7
0
0
0
0
System.arraycopyメソッドを使うときは、コピー先の配列は必ずコピー元より大きなものを用意してください。サイズが小さいとコピー時に範囲外にアクセスして、後述する「例外」が出ます。また、最後の引数にはコピー元配列の要素数を指定してください。誤ってコピー先配列の要素数を指定すると、こちらも同様に例外が出てしまいます。
配列のすべての要素を文字列に変換しておくと、テキストやファイルなどの形で出力するときに便利なことがあります。下記の構文でArrays.toStringメソッドを活用すると、配列を簡単に文字列に変換できます。
変換先の文字列変数 = Arrays.toString(変換したい配列);
Arrays.toStringメソッドは戻り値で結果を返すため、String変数でこれを受けてください。引数には文字列に変換したい配列を指定します。Arrays.toStringメソッドを使って、int型配列を文字列に変換するサンプルプログラムを見ていきましょう。なお、Arrays.toStringを使用する際は、java.util.Arraysをimportする必要があります。
<サンプルプログラム>
import java.util.Arrays; // java.util.Arraysをimportする
public class Test {
public static void main(String[] args) {
int[] numbers = {0, 9, 1, 8, 2, 7}; // 配列を生成する
String text = Arrays.toString(numbers); // 配列を文字列に変換する
System.out.println(text); // 変換後の文字列を表示する
}
}
<実行結果>
[0, 9, 1, 8, 2, 7]
なお、文字列の配列をひとつの文字列に変換したいこともあるでしょう。その場合は下記の構文でString.joinメソッドを活用すると、配列を簡単に文字列に変換できます。
String.join(区切り文字, 配列名);
最初の引数には、コンマやスペースなど任意の区切り文字を指定してください。今回は配列を「,」で区切った文字列にするサンプルプログラムを紹介します。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
String[] name ={“ニュートラルさん”, “Aさん”, “Xさん”, “ワークスさん”};
String text = String.join(“,”, name);
System.out.println(text);
}
}
<実行結果>
ニュートラルさん,Aさん,Xさん,ワークスさん
配列を文字列に変換する必要があるケースは非常に多いため、ぜひこれらのテクニックを身につけておきましょう。
配列を並び替える「ソート」が必要な場合は、下記の構文でArrays.sortメソッドを使用すると配列を簡単にソートできます。
Arrays.sort(ソートしたい配列, ソート順序);
2番目の引数については、主に「昇順(小さい順)」もしくは「降順(大きい順)」を指定します。昇順の場合は何も指定する必要はありませんが、降順の場合はCollections.reverseOrderメソッドを指定します。配列をソートするサンプルプログラムを見ていきましょう。
<サンプルプログラム>
import java.util.Arrays; // java.util.Arraysをimportする
import java.util.Collections; // java.util.Collectionsをimportする
public class Test {
public static void main(String[] args) {
int[] numbers1 = {0, 9, 1, 8, 2, 7}; // 配列を生成する
// 配列を昇順でソートする
Arrays.sort(numbers1);
// 昇順ソートの結果を表示する
System.out.println(“昇順ソートの結果”);
for(int i=0; i<numbers1.length; i++) {
System.out.println(numbers1[i]);
}
// Collections.reverseOrderメソッドは「基本データ型」の
// 配列には使えないため、新たにInteger型で配列を生成し直す
Integer[] numbers2 = {0, 9, 1, 8, 2, 7};
// 配列を降順でソートする
Arrays.sort(numbers2, Collections.reverseOrder());
// 降順ソートの結果を表示する
System.out.println(“降順ソートの結果”);
for(int i=0; i<numbers2.length; i++) {
System.out.println(numbers2[i]);
}
}
}
<実行結果>
昇順ソートの結果
0
1
2
7
8
9
降順ソートの結果
9
8
7
2
1
0
なお、降順ソートのためのCollections.reverseOrderメソッドは、配列が基本データ型の場合は使用できません。そのため、サンプルプログラムのように基本データ型をラップした、IntegerやDoubleなどの型を使う必要があります。
配列から特定の値を検索したい場合は、「forループによる線形探索」と「List.containsメソッド」の2つの方法があります。線形探索とは、forループで配列の先頭から末尾まで探り、目的の値と一致するかを調べる方法です。List.containsメソッドは下記の構文で使用します。
if(Arrays.asList(検索したい配列).contains(検索したい要素)){
目的の値が見つかった場合の処理
}
List.containsメソッドはListのメソッドなので、まずはArrays.asListメソッドで配列をListに変換する必要があります。List.containsメソッドは基本データ型は使用できないため、IntegerやDoubleなどのオブジェクト型の配列が必要です。
forループによる線形探索とList.containsメソッドで、配列から特定の値を検索するサンプルプログラムは下記のとおりです。
<サンプルプログラム>
import java.util.Arrays; // java.util.Arraysをimportする
public class Test {
public static void main(String[] args) {
int[] numbers1 = {0, 9, 1, 8, 2, 7}; // 配列を生成する
// forループによる線形探索で配列から特定の値を検索する
for(int i=0; i<numbers1.length; i++) {
if(numbers1[i] == 1) {
System.out.println(“「1」を検出!”);
}
}
// List.containsメソッドは「基本データ型」の配列には使えないため
// 新たにInteger型にて配列を生成し直す
Integer[] numbers2 = {0, 9, 1, 8, 2, 7};
// Arrays.asListメソッドによる線形探索で配列から特定の値を検索する
if(Arrays.asList(numbers2).contains(1)) {
System.out.println(“「1」を検出!”);
}
}
}
<実行結果>
「1」を検出!
「1」を検出!
List.containsメソッドは、使用前に配列をListに変換しないといけません。そのため、Listへの変換が面倒であれば、forループによる線形探索でも問題ないでしょう。
2つ以上の配列を結合させたい場合は、配列に要素を追加する場合と同じようにSystem.arraycopyメソッドを使用します。具体的には、連結させたい配列の要素すべてが入る大きな配列を用意して、そこにコピーするという手順です。サンプルプログラムを確認してみましょう。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
int[] numbers1 = {0, 1, 2, 3, 4}; // 1つ目の配列を生成する
int[] numbers2 = {10, 11, 12, 13, 14}; // 2つ目の配列を生成する
int[] numbers3 = {100, 101, 102, 103, 104}; // 3つ目の配列を生成する
// すべての配列の合計要素数を算出して、そのサイズの大きなコピー先配列を生成する
int length = numbers1.length + numbers2.length + numbers3.length;
int[] concatenated = new int[length];
// concatenated(連結配列)にnumbers1の要素をコピーする
// コピーする場所はconcatenated(連結配列)の最初
System.arraycopy(numbers1, 0, concatenated, 0, numbers1.length);
// 連結した配列にnumbers2の要素をコピーする
// コピーする場所は先ほどnumbers1をコピーした直後
System.arraycopy(numbers2, 0, concatenated, numbers1.length, numbers2.length);
// 連結した配列にnumbers3の要素をコピーする
// コピーする場所はconcatenated(連結配列)の最初
// コピーする場所は先ほどnumbers2をコピーした直後
System.arraycopy(numbers3, 0, concatenated, numbers1.length + numbers2.length, numbers3.length);
for(int i=0; i<concatenated.length; i++) {
System.out.println(concatenated[i]);
}
}
}
<実行結果>
0
1
2
3
4
10
11
12
13
14
100
101
102
103
104
注意が必要なのはSystem.arraycopyメソッドの4番目の引数です。ここには、連結した配列のどの部分に要素をコピーするかを示します。1つ目の配列のコピー時は、先頭からデータをコピーするため「0」で構いません。
しかし、2つ目の配列は1つ目の後ろにコピーするため、1つ目の配列の長さを指定します。さらに、3つ目の配列をコピーするときは、1つ目と2つ目の配列の合計の長さを指定することで、正しい場所にデータをコピー可能。ここを誤ると配列が正しく連結されなかったり、配列の範囲外へのアクセスで例外が出たりするので注意してください。
先ほど紹介した通常の配列は「一次元配列」と呼ばれますが、次元を追加した「多次元配列」が必要な場面も多いです。本章では、多次元配列の概要や活用方法について、下記3つの観点から解説します。
これまでの配列は、直線状に並んだ「一次元配列」です。これに対して、縦と横の平面上に並べたものを「二次元配列」、さらに奥行きも加えて立体的にしたものを「三次元配列」と呼びます。二次元配列や三次元配列など、複数の次元を持つ配列が「多次元配列」です。
Javaでは、配列の中に配列を格納することで多次元配列を実現しています。配列の各要素に別の一次元配列を格納したものが二次元配列、配列の各要素に別の二次元配列を格納したものが三次元配列という具合です。
少し分かりにくいかもしれませんが、「配列の配列」が多次元配列だと考えればイメージしやすくなるでしょう。
多次元配列の書き方は基本的には一次元配列と同じです。下記のように、二次元配列は「[]」を2つ並べて表現し、三次元配列は「[]」を3つ並べて表現します。new演算子で配列を生成するときも同じです。
型名 二次元配列名 = new 型名[二次元目の要素数][一次元目の要素数]
型名 三次元配列名 = new 型名[三次元目の要素数][二次元目の要素数][一次元目の要素数]
重要なポイントは、new演算子で配列を生成するときの要素数の位置関係です。右側には低次元、左側には高次元の要素数を記載してください。
int[][] numbers = new int[3][5];
上記の二次元配列では、numbers[0]からnumber[2]には、それぞれint[5]の一次元配列(要素数5のint型配列)が格納されています。
int[][][] numbers = new int[3][5][7];
上記の三次元配列では、numbers[0]からnumber[2]には、それぞれint[5][7]の二次元配列(二次元目の要素数5・一次元目の要素数7のint型二次元配列)が格納されています。
多次元配列は直感的にイメージするのが非常に難しいですが、実際にプログラムを組んでみると少しずつ理解できるようになるはずです。
多次元配列を理解するためには、実際に使って慣れることが最も重要です。そこで、多次元配列を使ったサンプルプログラムを見ていきましょう。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
// int型の二次元配列を生成する
int[][] numbers = new int[3][5];
// int[0]の各要素を初期化する
numbers[0][0] = 0;
numbers[0][1] = 1;
numbers[0][2] = 2;
numbers[0][3] = 3;
numbers[0][4] = 4;
// int[1]の各要素を初期化する
numbers[1][0] = 10;
numbers[1][1] = 11;
numbers[1][2] = 12;
numbers[1][3] = 13;
numbers[1][4] = 14;
// int[2]の各要素を初期化する
numbers[2][0] = 100;
numbers[2][1] = 101;
numbers[2][2] = 102;
numbers[2][3] = 103;
numbers[2][4] = 104;
// 配列内のすべての要素を表示する
for(int j=0; j<3; j++) {
for(int i=0; i<5; i++) {
System.out.println(numbers[j][i]);
}
}
}
}
<実行結果>
0
1
2
3
4
10
11
12
13
14
100
101
102
103
104
上記のサンプルプログラムでは、numbers[0]からnumbers[3]の各要素にはint[5]の配列(要素数5の一次元配列)が格納されています。そのため、各要素を表示するforループも二段構成になっているので、ひとつずつ丁寧に確認していきましょう。
外側のforループ内では、二次元目(int[3]の部分)を順番に確認しています。そのため、外側のforループ内で扱う変数は一次元配列です。さらに、内側のforループ内では一次元配列のときと同じように、通常のint型の変数にアクセスして要素を表示しています。
ただし、通常のfor文で多次元配列を扱うと、要素数やアクセス方法などのミスが多くなるため、多次元配列では拡張for文の使用をおすすめします。拡張for文の構文は下記のとおりです。
for(型名 変数名: 配列名) {
実行する処理
}
配列の要素を表示する部分を拡張for文に置き換えると下記のようになります。なお、forループ内での変数が「second」や「first」になっていますが、任意の名称に変更して構いません。
// 「拡張for文」で配列内のすべての要素を表示する
for(int[] second: numbers) {
for(int first: second) {
System.out.println(first);
}
}
拡張for文はさまざまな場面で便利に使えるため、今から慣れておいて決して損はありません。これまで紹介したサンプルプログラムのfor文を、拡張for文に置き換えてみましょう。
Javaの配列は非常に便利な機能ではありますが、使い方を誤ると予期せぬバグや例外の発生に繋がります。本章では、Javaの配列を使う際におさえておくべき注意点を3つ解説します。
Javaの配列では、範囲外にアクセスすると例外が出ます。例外とは「プログラム側で探知できるエラー」のこと。例外にはさまざまなものがありますが、配列の例外で特に多いのが「範囲外アクセス」です。
具体的には、インデックスの数値が配列の要素数以上になると、プログラムが例外を発生させます。実際にサンプルプログラムを作って見ていきましょう。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
int[] array = new int[5];
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;
array[5] = 6; // 範囲外なのでNG
}
}
<実行結果>
Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 at Test.main(Test.java:11)
実行結果には、「ArrayIndexOutOfBoundsExceptionが発生しました」という意味のメッセージが表示されています。要素数が「5」である配列に対し、その範囲を超えるインデックス5(6番目の要素)にアクセスしようとしたためです。
「ArrayIndexOutOfBoundsException」は、配列のサイズを超える部分にアクセスしようとしたときに発生する例外です。配列でアクセス可能な最後の要素は、どのような場合でも「要素数 – 1」なので注意してください。
なお、配列の生成後にサイズを拡張したい場合は、「配列に要素を追加する」の項目で紹介したようにSystem.arraycopyメソッドを使用しましょう。
Javaでは配列の要素をあとから変更することはできません。たとえば、要素数5の配列を要素数7に拡張したり、要素数3に縮小したりすることは不可能です。
配列の要素数を変更したい場合は、新しい配列を用意してから、そこにSystem.arraycopyメソッドでデータをコピーするのが基本です。しかし、要素数を変更するたびに配列を作り直すのは面倒ですよね。
よりフレキシブルにデータを扱いたい場合は、「ArrayList」という「可変長配列」を使うことをおすすめします。ArrayListを使用するサンプルプログラムは下記のとおりです。なお、ArrayListの使用にはjava.util.ArrayListのimportが必要です。
<サンプルプログラム>
import java.util.ArrayList; // java.util.ArrayListをimportする
public class Test {
public static void main(String[] args) {
// ArrayListをインスタンス化(生成)する
ArrayList<Integer> numbers = new ArrayList<Integer>();
// ArrayListにデータを追加する
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);
numbers.add(50);
System.out.println(“5つの要素を追加した後のデータ”);
// 拡張for文でArrayListの全要素を表示する
for(Integer i: numbers) {
System.out.println(i);
}
// ArrayListから3番目の要素を削除する
numbers.remove(2);
System.out.println(“3番目の要素を削除した後のデータ”);
// 拡張for文でArrayListの全要素を表示する
for(Integer i: numbers) {
System.out.println(i);
}
}
}
<実行結果>
5つの要素を追加した後のデータ
10
20
30
40
50
3番目の要素を削除した後のデータ
10
20
40
50
上記のサンプルプログラムのように、ArrayListはaddメソッドやremoveメソッドを使えば、要素の追加や削除が自由に行えます。ただし、ArrayListは通常の配列のように「[]演算子」で各要素にアクセスすることはできません。少し慣れが必要ではありますが、配列の要素数が頻繁に変動する場合はぜひ使ってみてください。
Javaの配列は「参照型変数」です。配列をコピーするときは「シャローコピー」が行われるため、メソッドの引数として配列を渡すときや、配列をコピーするときは注意が必要です。この点を理解しておかないと、プログラムに思わぬ不具合が生じることがあります。
Javaでは変数のコピーするときの挙動が、変数の種類によって「ディープコピー」と「シャローコピー」に分かれます。ディープコピーでは、変数のデータそのものがコピーされます。一方で、コピー元とコピー先の変数を「関連づける」ことで、間接的にコピーを実現するのがシャローコピーです。
Javaの配列ではシャローコピーが行われるため、コピー元とコピー先いずれかのデータを変更すると、他方のデータも変更されてしまいます。これを下記のサンプルプログラムで実証してみましょう。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
// コピー元配列を生成するj
int[] numbers1 = {1, 2, 3};
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
// コピー先配列にデータをコピーする
int[] numbers2 = numbers1;
// コピー先配列のデータを表示する
System.out.println(“コピー先配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers2[i]);
}
System.out.println();
System.out.println(“=====コピー先配列のデータ書き換え=====”);
System.out.println();
// コピー先配列のデータを書き換える
numbers2[0] = 100;
numbers2[1] = 200;
numbers2[2] = 300;
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
System.out.println();
System.out.println(“=====メソッド内で配列データの書き換え=====”);
System.out.println();
// コピー先配列のデータをメソッドに引き渡す
function(numbers2);
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
}
static void function(int[] numbers) {
// 配列のデータを書き換える
numbers[0] = 10000;
numbers[1] = 20000;
numbers[2] = 30000;
}
}
<実行結果>
コピー元配列のデータ
1
2
3
コピー先配列のデータ
1
2
3
=====コピー先配列のデータ書き換え=====
コピー元配列のデータ
100
200
300
=====メソッド内で配列データの書き換え=====
コピー元配列のデータ
10000
20000
30000
サンプルプログラムのように、配列を別の変数にコピーしてからコピー先のデータを書き換えると、コピー元のデータも変更されてしまいます。また、メソッドへの引き渡し時もシャローコピーが行われるため、メソッド内での操作でコピー元のデータも変更されます。
「コピー元のデータを保持したい」場合は、System.arraycopyメソッドやcloneメソッドを使用しましょう。System.arraycopyメソッドの使い方は前述したとおりなので、ここではcloneメソッドを使って先ほどのプログラムを書き換えてみます。
<サンプルプログラム>
public class Test {
public static void main(String[] args) {
// コピー元配列を生成するj
int[] numbers1 = {1, 2, 3};
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
// コピー先配列にデータをコピーする
int[] numbers2 = numbers1.clone();
// コピー先配列のデータを表示する
System.out.println(“コピー先配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers2[i]);
}
System.out.println();
System.out.println(“=====コピー先配列のデータ書き換え=====”);
System.out.println();
// コピー先配列のデータを書き換える
numbers2[0] = 100;
numbers2[1] = 200;
numbers2[2] = 300;
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
System.out.println();
System.out.println(“=====メソッド内で配列データの書き換え=====”);
System.out.println();
// コピー先配列のデータをメソッドに引き渡す
function(numbers2.clone());
// コピー元配列のデータを表示する
System.out.println(“コピー元配列のデータ”);
for(int i=0; i<3; i++) {
System.out.println(numbers1[i]);
}
}
static void function(int[] numbers) {
// 配列のデータを書き換える
numbers[0] = 10000;
numbers[1] = 20000;
numbers[2] = 30000;
}
}
<サンプルプログラム>
コピー元配列のデータ
1
2
3
コピー先配列のデータ
1
2
3
=====コピー先配列のデータ書き換え=====
コピー元配列のデータ
1
2
3
=====メソッド内で配列データの書き換え=====
コピー元配列のデータ
1
2
3
配列名の後ろに「.clone()」を付けるだけで、配列がディープコピーされます。Javaの配列でシャローコピーが行われる理由を知るためには、「参照型変数」について深く学ぶ必要があります。参照型変数は分かりにくい概念ですが、Javaをマスターするために欠かせないので、別の機会に改めて解説します。
Javaの配列は「同じデータ型の変数をまとめて扱いたいとき」に使うと便利です。配列でデータを管理することにより、通常の変数では困難な処理が簡単にできるようになります。
ただし、配列は通常の変数とは異なる点があります。new演算子を使って生成する必要があることや、最初に決めた要素数を変更できないことに注意してください。また、配列は参照型変数なので、コピー時のデータの取り扱い方もポイントになります。
Javaの配列は、機能が多いだけに学ぶことも多いため、初心者には難しいところもあります。しかし、今回解説したサンプルプログラムを1行ずつトレースしていけば、基本はもちろん応用的な活用法までマスターできるはずです。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール