はじめに
自己紹介
皆さん、こんにちは、Udemy講師の斉藤賢哉です。私はこれまで、25年以上に渡って企業システムの開発に携わってきました。特にアーキテクトとして、ミッションクリティカルなシステムの技術設計や、Javaフレームワーク開発などの豊富な経験を有しています。
様々なセミナーでの登壇や雑誌への技術記事寄稿の実績があり、また以下のような書籍も執筆しています。
いずれもJava EE(Jakarta EE)を中心にした企業システム開発のための書籍です。中でも 「アプリケーションアーキテクチャ設計パターン」は、(Javaに限定されない)比較的普遍的なテーマを扱っており、内容的にはまだまだ陳腐化していないため、興味のある方は是非手に取っていただけると幸いです(中級者向け)。
Udemy講座のご紹介
この記事の内容は、私が講師を務めるUdemy講座『Java Basic編』の一部の範囲をカバーしたものです。『Java Basic編』はこちらのリンクから購入できます(セールス対象外のためいつも同じ価格)。また定価の約30%OFFで購入可能なクーポンをQiita内で定期的に発行していますので、興味のある方は、ぜひ私の他の記事をチェックしてみてください。
この講座は、以下のような皆様にお薦めします。
- Javaの言語仕様や文法を正しく理解すると同時に、現場での実践的なスキル習得を目指している方
- 新卒でIT企業に入社、またはIT部門に配属になった、新米システムエンジニアの方
- 長年IT部門で活躍されてきた中堅層の方で、学び直し(リスキル)に挑戦しようとしている方
- 今後、フリーランスエンジニアとしてのキャリアを検討している方
- 「Chat GPT」のエンジニアリングへの活用に興味のある方
- 「Oracle認定Javaプログラマ」の資格取得を目指している方
- IT企業やIT部門の教育研修部門において、新人研修やリスキルのためのオンライン教材をお探しの方
この記事を含むシリーズ全体像
この記事はJava SEの一部の機能・仕様を取り上げたものですが、一連のシリーズになっており、シリーズ全体でJava SEを網羅しています。また認定資格である「Oracle認定Javaプログラマ」(Silver、Gold)の範囲もカバーしています。シリーズの全体像および「Oracle認定Javaプログラマ」の範囲との対応関係については、以下を参照ください。
5.1 配列
チャプターの概要
このチャプターでは、データをまとめて管理する仕組みである配列について学びます。
なお実際のJavaによるアプリケーション開発では、データをまとめて管理する場合は、配列よりも「コレクション」という機能を使うケースが大半です。ただし配列はメソッドの可変引数(チャプター7.3)など、広い用途で活用されています。また学習効果も高く、配列の考え方はコレクションを理解する上での前提にもなるので、このレッスンにて丁寧に説明します。
5.1.1 配列の概念
配列とは
配列とは、同じ型を持つ複数のデータをまとめて保持し、一括して処理するための特殊なデータ型です。変数がデータを格納するための「入れ物」だとしたら、配列はその「入れ物」が「束ねられたもの」と考えてもらえると分かりやすいでしょう。
複数のデータがある場合、個々のデータをそれぞれ個別の変数に格納することはできますが、型が同じ複数のデータを一括して処理したい場合は、配列を利用すると圧倒的に効率的です。配列は、Javaに限らず殆どのプログラミング言語における共通的な概念です。
配列の利用目的
例えばAlice、Bob、Carolの3人による、TOIECテストの平均スコアを求める処理を考えてみましょう。この処理を行うためには、まず3人のスコアを一時的に変数に格納し、次に3つの変数の合計値を算出し、それを人数である3で割ることで、平均スコアを求めることになります。具体的には、以下のようなコードになるでしょう。
int aliceScore = 650;
int bobScore = 570;
int carolScore = 700;
double sum = aliceScore + bobScore + carolScore;
double avg = sum / 3;
ただしこの処理には、大きな課題があります。もしこの3人に、Dave、Ellenを加えて、5人の平均スコアを求める必要が生じた場合、どのような修正が発生するでしょうか。DaveとEllenのスコアを追加する処理は必須だとしても、合計値を求める処理や、平均スコアを求める処理まで、広い範囲に修正が及ぶことになってしまいます。
配列は、このように何らかの同じ型を持つ複数のデータを一括して処理するために利用します。
上記と同じ処理を、配列とfor文によるループで実装すると、以下のコードになります。
int[] scores = {650, 570, 700}; //【1】
double sum = 0;
for (int i = 0; i < scores.length; i++) {
sum += scores[i];
}
double avg = sum / scores.length;
このコードの詳細は、この後、順次解説していきます。注目するべき点は、このコードであれば後からDaveとEllenが追加された場合、配列scoresへのデータ追加の処理【1】は必要ですが、それ以降に登場する配列scoresに対する処理は修正不要のため、データの増減に応じた柔軟な処理が可能になるという点です。アプリケーション開発をする上で、配列が如何に強力な武器になるか、イメージはつかんでもらえたのではないでしょうか。
このように配列は、for文によるループと組み合わせることで威力を発揮します。なおループについてはチャプター6.3にて取り上げます。
実際のJavaアプリケーション開発では、データをまとめて管理する場合、配列と同じ考え方を取り入れた「コレクション」という機能を使うケースが大半です(チャプター18.1参照)。ただし配列は、メソッドの可変引数やメインメソッドの引数、また旧来からある外部ライブラリのAPIなど、様々な用途で活用されています。
5.1.2 配列の宣言と初期値
配列の宣言
配列は通常の変数と同じように、まず宣言を行い、次に初期化し、さらに格納された値を参照して処理を行います。
配列はデータ型の後ろに[]
を付与する形で、以下のように宣言します。
データ型[] 変数名;
例えばint型配列の場合は、以下のコードのようになります。
int[] scores;
なお配列の宣言では、変数名の後ろに[]
を付与する記法(int scores[]
)も認められていますが、本コースではデータ型の後ろに[]
を付与する形式で統一します。
配列の初期化
配列には、サイズという考え方があります。配列とは前述したように「データを格納する入れ物を束ねたもの」ですが、配列のサイズとは「束ねられた入れ物の大きさ」を意味します。
配列を宣言しただけでは、配列のサイズはまだ決まっていません。配列はサイズを指定した上で、以下のように初期化します。
変数名 = new データ型[サイズ];
この構文におけるnew
は、配列型やクラス型の変数を初期化するための演算子ですが、詳細はチャプター7.1で取り上げます。具体的には以下のコードのようになります。
int[] scores; // 配列を宣言
scores = new int[3]; // 配列を初期化(サイズは3)
このようにして配列は初期化します。この時点では「サイズ3の入れ物」が用意されただけであり、個々の「入れ物」の中身は空のままです。
配列に初期値を明示的に格納するためには、配列の個々の「入れ物」にアクセスする必要がありますが、その方法は次のレッスンで説明します。
初期化子による配列の初期化
前述したような方法で配列を宣言して初期化しても、「入れ物」が確保されるだけであり、初期値は格納されません。ただし初期化子を使うと、配列の宣言、初期化、および初期値の格納を同時に行うことができます。
初期化子による配列の初期化は、以下のような構文になります。
データ型[] 変数名 = { 値1, 値2, 値3, .... }
このように{ }
の中に初期値をカンマで区切って列挙します。具体的には以下のコードのようになります。
int[] scores = {650, 570, 700};
これで、サイズが3のint型配列scoresが宣言され、初期値として3つの値が格納されました。このような配列のデータ構造を図で示すと、以下のようになります。
配列をローカル変数として使用する場合など、配列の宣言から初期値の明示的な格納までを同時に行いたい場合は、配列の初期化はこの方法を選択すると良いでしょう。
5.1.3 配列の要素
配列の要素とインデックス
配列では、用意された「入れ物」に格納された個々の値を「要素」と呼びます。配列内の各要素にアクセスするためには、配列内の位置を表すインデックスが必要です。インデックスは0から始まる整数で、例えばサイズが3の配列の場合は、0、1、2という3つのインデックスがあります。
配列の各要素は、以下のようにインデックスによって参照します。
配列名[インデックス]
それではコードで確認してみましょう。
int[] scores = {650, 570, 700};
int x = scores[0];
インデックスに0を指定すると、配列scoresに格納された最初の要素の値が取り出され、変数xには650が格納されます。
繰り返しになりますが、インデックス0が要素の一番目になる、という点を改めて強調しておきます。
また、以下のようにして要素に値を格納します。
配列名[インデックス] = 値
レッスン5.1.2で、newキーワードによって初期化する方法を説明しましたが、その時点では初期値は格納されていません。その場合は、この方法で初期値を明示的に格納します。具体的には以下のコードのようにします。
int[] scores = new int[3];
scores[0] = 650;
scores[1] = 570;
scores[2] = 700;
また以下のコードのようにして、すでに値が格納されている要素に対して、上書きして別の値を再代入することも可能です。
int[] scores = {650, 570, 700};
scores[0] = 660;
このようにすると、配列scoresのインデックス0には660が格納されます。
配列のサイズ
配列のサイズは、一度決まったら、後から拡大したり縮小したりすることはできません。配列のサイズを調べるためには、以下のように配列の後ろに.
を付けて、lengthというキーワードを付与します。これを配列のlengthプロパティと呼びます。
配列名.length
具体的には以下のコードを見てください。
int[] scores = {650, 570, 700};
int lgh = scores.length; // 3
このコードを実行すると、変数lghには3が格納されます。
またインデックスの範囲を超えた配列へのアクセスがあった場合には、ArrayIndexOutOfBoundsExceptionという例外(エラーの一種)が発生します。例外についてはチャプター19.1で説明しますが、ここでは広い意味でエラーの一種と理解しておけば問題ありません。
例えば以下のコードを実行すると、どうなるでしょうか。
int[] scores = {650, 570, 700};
int x = scores[3];
変数scoresのサイズは3であり、インデックスの範囲は0から2までとなります。従って変数scoresのインデックスに3を指定すると、ArrayIndexOutOfBoundsException例外が発生します。
5.1.4 多次元配列
多次元配列とは
これまで登場した配列は、インデックスが一種類しかない一次元配列でした。それに対して多次元配列とは、インデックスが二種類以上になる配列です。
ここでは二次元の配列を取り上げます。二次元配列は、データ型の後ろに[]
を2つ付与する記法で、以下のように宣言します。
データ型[][] 変数名 = {{値1, 値2, 値3}, {....}, {....}, ....};
二次元配列は表形式のようなデータ構造になりますが、各行の長さが不ぞろいであっても問題ありません。
実際のJavaによるアプリケーション開発では、配列よりもコレクションを利用するのが一般的、という話をしました。このレッスンで紹介する多次元配列は配列ならではの専売特許というわけではなく、コレクションを利用する場合でも、多次元配列に相当するデータ構造を作ることは可能です。
多次元配列の要素へのアクセス
今度は、Alice、Bob、Carolの3人について、国語、算数、理科、社会の4科目のスコアを、二次元配列によって保持するものとします。この場合、一次元目の配列がAliceから始まる「人の配列」になり、個々の配列の中に内包される二次元目の配列が、国語から始める「科目の配列」になります。
具体的には以下のコードのようになります。
int[][] scoreTable = {{80, 90, 100, 85}, {75, 70, 75, 60}, {95, 65, 75, 90}};
このような二次元配列のデータ構造は、図で表すと以下のようになります。
二次元配列の中にある、外側に位置する一次元目の配列には、通常の配列と同じようにアクセスします。
例えばBobの全科目の点数を配列として取得したい場合は、以下のコードのようにします。
int[][] scoreTable = {{80, 90, 100, 85}, {75, 70, 75, 60}, {95, 65, 75, 90}};
int[] scores = scoreTable[1];
このコードは、配列scoreTableに対してインデックス1を指定していますので、一次元目の配列から、2番目の要素であるBobの科目配列を取り出しています。
次に二次元配列の各要素には、インデックスによって以下のようにアクセスします。
配列名[インデックス1][インデックス2]
例えばBobの理科の点数を取得したい場合は、どのようにしたら良いでしょうか。以下のコードを見てください。
int[][] scoreTable = {{80, 90, 100, 85}, {75, 70, 75, 60}, {95, 65, 75, 90}};
int s = scoreTable[1][2];
Bobのデータは、一次元目の配列においてインデックス1であり、その中における理科のデータはインデックス2です。従ってBobの理科のスコアは、scoreTable[1][2]
によって取得することができます。
多次元配列のサイズ
多次元配列のサイズを調べる方法も、通常の配列と同じようにlengthプロパティを使いますが、一次元目のサイズなのか、二次元目のサイズなのかによって、lengthプロパティの指定の仕方が変わります。以下のコードを見てください。
int[][] scoreTable = {{80, 90, 100, 85}, {75, 70, 75, 60}, {95, 65, 75, 90}};
int lgt1 = scoreTable.length; //【1】
int lgt2 = scoreTable[0].length; //【2】
多次元配列そのものにlengthプロパティを指定する【1】と、一次元目の配列サイズが返されるため、変数lgt1は3になります。一方、多次元配列内からインデックスによって取り出した配列にlengthプロパティを指定する【2】と、二次元目の配列サイズが返されるため、変数lgt2は4になります。
このチャプターで学んだこと
このチャプターでは、以下のことを学びました。
- 配列の概念や利用目的について。
- 配列の宣言や初期化の方法、初期化子の記述方法について。
- 配列の要素へのアクセス方法やインデックス、サイズについて。
- 多次元配列の考え方や、各要素へのアクセス方法について。