こんにちは、運営者のハックです。
今回はデイトラJava Silver対策編「第5章の対策」の学習記録を紹介します。
第5章 配列の操作
第5章では主に「配列の宣言、初期化、インスタンスの生成」「多次元配列」「配列のコピー」について解いていきます。
今回も引き続き解説の参考と例題の引用は【黒本】と呼ばれる「徹底攻略Java SE 11 Silver問題集」から行っていきます。
配列の宣言、初期化、インスタンスの生成
配列は複数のデータを一つの変数に格納するためのデータ構造で、同じデータ型の要素をメモリ上に連続して保持します。。配列を使用することで、同じデータ型の多数の要素を一つの変数名で管理できます。
Javaの配列は「配列クラス」から作られた「インスタンス」であり、値の集合を扱うことを目的としています。値そのものと、値の集合を扱うインスタンスは異なるものであることに注意が必要です。配列が扱う値のことを「要素」と言います。
配列の宣言
配列を宣言するには、要素の型に続けて [] を使用します。これによって、その型のデータを複数持つ配列であることを示します。
//基本形:データ型[] 配列変数名;
//例
int[] myArray; // 整数型の配列を宣言
String[] stringArray; // 文字列型の配列を宣言
ここで、データ型
は配列が格納する要素の型(例えば int, double, String など)を指し、[]はこの変数が配列であることを示します。配列変数名はその配列の名前です。
なお、[]はデータ型の後ろに記述するだけでなく、「int array[];」のように変数名の後ろに記述することもできます。
配列宣言の特徴
- 型の一貫性:
配列は一種のコンテナとして機能し、宣言されたデータ型の要素のみを格納できます。例えば、int[] 配列は整数のみを格納し、String[] 配列は文字列のみを格納します。 - 固定サイズ:
配列はその長さが固定されているという特徴があります。一度配列のサイズを定義すると、実行時にそのサイズを変更することはできません。新しい要素を追加するには、新しい配列を作成する必要があります。 - 0から始まるインデックス:
Javaの配列では、最初の要素が0番目のインデックスに位置します。これは多くのプログラミング言語で採用されている標準的なアプローチです。 - 連続したメモリ割り当て:
配列の要素はメモリ上で連続して配置されます。これにより、任意の要素へのアクセス時間が一定で、高速なデータアクセスが可能となります。
配列の初期化
配列を初期化するには、具体的な要素の数を指定するか、直接初期値を設定します。配列の宣言と初期化は別のステップで行うことも、同時に行うこともできます。配列を初期化する際の基本的な方法は次のとおりです。
静的初期化
- 配列を宣言と同時に特定の値で初期化します。初期化子「{}」を使います。
- この方法は、配列のサイズと具体的な初期値が明確な場合に便利です。
int[] numbers = {10, 20, 30, 40, 50}; // 配列の宣言と同時に初期化
動的初期化
- 配列を宣言した後で、新たに new キーワードを使って配列のサイズを指定し、メモリを割り当てます。
- この方法は、配列のサイズが動的に決定される場合や、初期値が後から設定される場合に使用します。
double[] prices = new double[5]; // 5つの要素を持つdouble型の配列を動的に初期化
配列初期化の特徴と利点
- デフォルト値:
- 数値型の配列 (int, double など) は
0
または 0.0 で初期化されます。 - boolean 型の配列は false で初期化されます。
- オブジェクト型の配列(例:String[])は null で初期化されます。
- 数値型の配列 (int, double など) は
- コードの簡潔化:
配列を初期化することで、後のコードで個々の要素に値を設定する手間が省けます。 - エラーの防止:
適切に初期化された配列は、NullPointerException のようなランタイムエラーを防ぐのに役立ちます。 - プログラムの効率化:
初期化により、配列がすぐに使用可能になり、プログラムのパフォーマンスが向上します。
配列のインスタンス生成
配列のインスタンス生成は、配列にメモリを割り当てて使用可能にする過程です。配列のインスタンス生成には new キーワードを使用します。
int[] numbers = new int[10]; // 10個の整数を格納できる配列を生成
String[] words = new String[5]; // 5個の文字列を格納できる配列を生成
配列インスタンス生成の特徴と注意点
- 初期値の自動設定:
配列をnew
で生成すると、自動的にその型のデフォルト値で初期化されます。 - 型安全性:
配列は指定した型のデータのみを格納でき、異なる型のデータを誤って格納するリスクを防ぎます。 - インデックス範囲の確認:
有効なインデックス範囲外へのアクセスを試みると、ArrayIndexOutOfBoundsExceptionが発生するので注意が必要です。
それでは実際の例題を解いてみましょう!
次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Item { 2. String name; 3. int price = 100; 4. }
1. public class Main{ 2. public static void main(String[] args) { 3. Item[] items = new Item[3]; 4. int total = 0; 5. for (int i = 0; i < items.length; i++) { 6. total += items[i].price; 7. } 8. System.out.println(total); 9. } 10. }
A. 0が表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 200が表示される。
C. 300が表示される。
D. コンパイルエラーが発生する。
E. 実行時に例外がスローされる。
「Item[] items = new Item[3];」は3つのitemを扱う配列インスタンスを作ったよってことだにゃ。forの条件式の「int i = 0; i < items.length; i++」はitemsの長さより小さい場合までiの値を増やすってことだよねぇ。そーすると、itemの容量は3だから0~2番目の配列を順番に取り出してpriceにどんどん足していけばいいのにゃ。
だから正解は選択肢Cだと思うんだけど…あにゃ?
なんか肝心な配列の中身がないような気がするのにゃ。
お察しの通り選択肢Cは誤りです。
初学者が誤りやすい事例として、オブジェクト型配列を生成したときに、オブジェクトそのものを同時に作っていると錯覚しがちなことが挙げられます。
Mainクラスの3行目「Item[] items = new Item[3];」はただ配列インスタンスを作ったにすぎず、itemのインスタンスを生成しているわけではありません。この配列インスタンスの要素はオブジェクト型配列のデフォルト値である「null」で初期化されています。そのため、6行目で「price」フィールドにアクセスする段階で、変数の参照先がないという意味のぬるぽ…いわゆる「NullPointerException」が発生し、例外がスローされます。選択肢Eが正解です。
ガッ!…って、ネタが古いのにゃ。
多次元配列
多次元配列の生成は、一次元配列よりも複雑なデータ構造を扱う場合に使用されます。特に科学技術計算、画像処理、情報の整理・管理など、多層的なデータを扱う場合にはその真価を発揮します。多次元配列は主に2次元配列(行列)、3次元配列(データキューブ)などがあります。
基本的な生成方法
多次元配列のインスタンスを扱う変数は、配列であることを表す大括弧[]を、次元の分だけ記述します。多次元配列の場合には、複数記述する大括弧を一度にまとめて記述する必要はなく、データ型と変数名の後ろに分けて記述することもできます。
//2次元配列変数の宣言
int[][] arrayA;
int[] arrayB[];
//3次元配列変数の宣言
int arrayC[][][];
int[][] arrayD[];
非対称な多次元配列
各行で列の数が異なる非対称な配列も生成可能であり、このような配列のことを「非対称な多次元配列」と呼ばれます。「ジャグ配列」とも言います。
//第一行は2列、第二行は3列、第三行は1列の配列を生成
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2];
jaggedArray[1] = new int[3];
jaggedArray[2] = new int[1];
多次元配列の特徴
- データ構造の複雑性:
- 多次元配列を使用することで、表形式のデータや、空間データなど、複雑なデータ構造を扱うことが可能になります。
- これにより、現実の問題や数学的なモデリングをプログラム上で表現しやすくなります。
- メモリ使用の最適化:
- 多次元配列は、必要なメモリ領域を連続的に確保することで、データの読み書き効率を高めます。
- しかし、大きな多次元配列を生成する場合はメモリの消費が大きくなるため、使用する際には注意が必要です。
- アクセス速度:
- 多次元配列の各要素へのアクセスは、インデックスを通じて直接行うことができるため、アクセス速度が速いです。
- 例えば matrix[2][3] は、3行目の4列目の要素を指します。
- 可読性と保守性:
- コードの可読性が高まり、データがどのように構造化されているか一目で理解しやすくなります。
- 多次元配列を使うことで、データに関連する操作を集約し、プログラムの保守が容易になります。
それでは多次元配列も実際の例題を解いてみましょう!
次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Main{ 2. public static void main(String[] args) { 3. String[][] array = {{"A", "B"}, null, {"C", "D", "E"}}; 4. int total = 0; 5. for (String[] tmp : array) { 6. total += tmp.array; 7. } 8. System.out.println(total); 9. } 10. }
A. 0が表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 5が表示される。
C. 9が表示される。
D. コンパイルエラーが発生する。
E. 実行時に例外がスローされる。
「String[][] array = {{“A”, “B”}, null, {“C”, “D”, “E”}}」の宣言で1次元目の配列には3つの要素が入っていて、2次元目の配列に文字列が入っている感じかにゃ。1次元目の1つ目の要素にはAとB,2つ目の要素にはnull、3つ目の要素にはC,D,Eの要素が入っているのにゃ。
「for (String[] tmp : array)」で「array」配列から各要素を順番に取り出して「total += tmp.array;」の文のとおり参照した値を「total」にどんどん足していくのにゃ。
…うにゃ?nullを参照?まさか、この例題もぬるぽが出現するのかにゃ!?
ぬるぽ発生の場合は選択肢Eが正解なのにゃ!
ガッ、正解です!
nullはどこも参照していないため「NullPointerException」が発生します。
配列のコピー
Javaにおける配列のコピーは、元の配列の内容を新しい配列に複製する処理です。これにはいくつかの方法があり、それぞれ特徴が異なります。
clone() メソッドを使用する方法
- 方法
- 配列オブジェクトに対して clone() メソッドを呼び出すことで、その配列の浅いコピー(shallow copy)を作成します。
- 「浅いコピー」とは、配列の各要素が直接持つ値(プリミティブ型)は複製されるが、要素が参照型の場合は参照がコピーされるだけで、参照しているオブジェクト自体は複製されないことを意味します。
- 特徴:
- 最も簡単で読みやすい方法。
- 配列の要素がプリミティブ型の場合、完全なコピーが得られる。
- 参照型の要素を持つ配列を浅くコピーするため、元の配列と新しい配列が同じオブジェクトを参照します。
int[] original = {1, 2, 3, 4, 5};
int[] copied = original.clone();
System.arraycopy() メソッドを使用する方法
- 方法:
- System.arraycopy() は、指定されたソース配列の特定の位置から、目的配列の特定の位置に、指定された数の要素をコピーします。
- 特徴:
- 非常に高速で効率的。
- 大量のデータを扱う場合や、配列の一部のみをコピーしたい場合に適しています。
- 同じく浅いコピーを行います(プリミティブ型は問題なくコピー、参照型は参照のみコピー)。
int[] original = {1, 2, 3, 4, 5};
int[] copied = new int[original.length];
System.arraycopy(original, 0, copied, 0, original.length);
//出力結果:[1, 2, 3, 4, 5]
Arrays.copyOf() または Arrays.copyOfRange() メソッドを使用する方法
- 方法:
- Arrays.copyOf() は元の配列の指定された長さまでの要素を新しい配列にコピーします。
- Arrays.copyOfRange() は元の配列の指定された範囲の要素を新しい配列にコピーします。
- 特徴:
- Arrays.copyOf() と Arrays.copyOfRange() は使いやすく、コピーの範囲を指定しやすい。
- 浅いコピーを提供し、プリミティブ型と参照型の挙動は clone() と同様です。
- Arrays.copyOf() は全体または一部の要素のみを新しい配列にコピーしたい場合に便利です。
int[] original = {1, 2, 3, 4, 5};
int[] copied = Arrays.copyOf(original, original.length); // 全部コピー
int[] partialCopied = Arrays.copyOfRange(original, 1, 4); // index 1 (含む) から index 4 (含まず) まで
書籍ではclone()を使用した例題がありますので、確認してみましょう。
次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Main{ 2. public static void main(String[] args) { 3. int[][] arrayA = {{ 1, 2 },{ 1, 2 }, { 1, 2, 3 }}; 4. int[][] arrayB = arrayA.clone(); 5. int total = 0; 6. for (int[] tmp : arrayB) { 7. for (int[] val : tmp) { 8. total += val; 9. } 10. } 11. System.out.println(total); 12. } 13. }
A. 0が表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 12が表示される。
C. コンパイルエラーが発生する。
D. 実行時に例外がスローされる。
clone()はプリミティブ型は完全コピーできるから、「int[][] arrayB = arrayA.clone();」では配列「{{ 1, 2 },{ 1, 2 }, { 1, 2, 3 }}」を完全にコピーしていると考えるのにゃ。
「for (int[] tmp : arrayB)」でと「for (int[] val : tmp)」で「arrayB」の配列から順番に要素を取り出しして、「total」に順番に足していけばいいのにゃ!
つまり1+2+1+2+1+2+3=12になるから、選択肢Bが正解だにゃ!
ねこ奈お見事です!考え方もオッケーですよ!
まとめ 配列は現場であまり使わないけど、JavaSilver試験には出ます
今回は「配列の宣言、初期化、インスタンスの生成」「多次元配列」「配列のコピー」について例題を踏まえて解説しました。
デイトラの講義の中で言われていたのですが、実際の現場ですと配列は「list」インターフェースで、多次元配列は「map」インターフェースを使用するので、配列の型自体はほぼ使われないそうです。
ただし、古いライブラリとか一部のフレームワークの機能の中では配列を返してくることがあるので、配列の使い方や変数宣言は知っておく必要があるとのことです。
しっかりと学んで習得しておきます。
以上で今回の学習記録を終えます。
ここまでご覧いただきありがとうございました。
コメント