こんにちは、運営者のハックです。
今回はデイトラJava Silver対策編「第6章の対策」のうち、後編の学習記録を紹介します。
※「値渡しと参照渡し」について解説が長くなったため、この項目のみ別途記事にしました。
引き続き「インスタンスとメソッド」について学習します
今回も引き続き解説の参考と例題の引用は【黒本】と呼ばれる「徹底攻略Java SE 11 Silver問題集」から行っていきます。
コンストラクタ
コンストラクタは、クラスのインスタンス(オブジェクト)を作るときに呼び出される特別なメソッドです。オブジェクトを初期化するために使われます。
簡単な例を見てみましょう。以下は「Person」という名前のクラスです。このクラスには名前と年齢という2つの情報を持つことができます。
public class Person {
String name;
int age;
// コンストラクタ
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// メソッド
public void introduce() {
System.out.println("私の名前は " + name + " です。年齢は " + age + " 歳です。");
}
}
コンストラクタのルール
- クラス名と同じ名前:
コンストラクタの名前はクラス名と同じでなければなりません。この例ではクラス名が「Person」なので、コンストラクタの名前も「Person」です。 - 戻り値がない:
コンストラクタには戻り値(intとかStringとか)はありません。戻り値型は記述できません。 - new演算子でコンストラクタを呼び出す:
newと一緒にしか使えません(インスタンス生成時以外は呼び出しができない)
コンストラクタの使い方
コンストラクタは、オブジェクトを作るときに自動的に呼び出されます。以下は、「Person」クラスを使って新しい人のオブジェクトを作る例です。
public class Main {
public static void main(String[] args) {
// コンストラクタを使ってオブジェクトを作成
Person person = new Person("太郎", 25);
// メソッドを呼び出して自己紹介
person.introduce();
}
}
//出力結果:私の名前は 太郎 です。年齢は 25 歳です。
このように、コンストラクタを使うことで、オブジェクトを作成するたびに必要な初期化処理を自動的に行うことができます。
デフォルトコンストラクタ
デフォルトコンストラクタとは、プログラマがクラスにコンストラクタを定義しなかった場合に、自動的に用意されるコンストラクタのことです。このコンストラクタは引数を持たず、何も特別な処理を行いません。
例えば、以下の例を見てみます。
public class Example {
// 何もコンストラクタを定義していない
}
public class Main {
public static void main(String[] args) {
Example example = new Example(); // デフォルトコンストラクタが呼び出される
}
}
上記のコードでは、Exampleクラスにはコンストラクタが定義されていませんが、new Example()でオブジェクトを作成できます。これは、Javaが自動的にデフォルトコンストラクタを用意しているためです。
自分でコンストラクタを定義した場合
もし自分でコンストラクタを定義した場合、デフォルトコンストラクタは自動的に作られません。そのため、引数なしでオブジェクトを作成したい場合は、自分でデフォルトコンストラクタを定義する必要があります。以下の例のように定義します。
public class Example {
// 引数付きのコンストラクタ
public Example(int value) {
// 何かの初期化処理
}
// デフォルトコンストラクタ
public Example() {
// 特別な初期化処理はしない
}
}
public class Main {
public static void main(String[] args) {
Example example1 = new Example(10); // 引数付きのコンストラクタが呼び出される
Example example2 = new Example(); // デフォルトコンストラクタが呼び出される
}
}
初期化子
初期化子とは、オブジェクトの生成時に特定の処理を実行するためのブロックです。初期化子には、インスタンス初期化子と静的(static)初期化子の2種類があります。
インスタンス初期化子はオブジェクト生成時に、静的初期化子はクラスロード時に実行されます。これに対して、コンストラクタはオブジェクトが生成されたときにのみ呼び出され、オブジェクトの具体的な初期設定を行います。
インスタンス初期化子
インスタンス初期化子は、オブジェクトが生成されるときに実行される初期化コードのブロックです。これは、コンストラクタが呼び出される前に実行されます。コンストラクタと共にオブジェクトの初期設定を行うために使われます。
例えば、以下の例を見てみます。
public class Example {
private int value;
{
// インスタンス初期化子
value = 10;
System.out.println("インスタンス初期化子が呼び出されました");
}
public Example() {
System.out.println("コンストラクタが呼び出されました");
}
public static void main(String[] args) {
Example ex = new Example(); // オブジェクトを生成する
}
}
//実行結果:
//インスタンス初期化子が呼び出されました
//コンストラクタが呼び出されました
この例では、Exampleクラスのオブジェクトが生成されると、まずインスタンス初期化子が実行され、その後でコンストラクタが実行されることがわかります。
静的(static)初期化子
静的初期化子は、クラスが初めてロードされたときに一度だけ実行される初期化コードのブロックです。静的変数を初期化するために使用されます。
例えば、以下の例を見てみます。
public class Example {
private static int staticValue;
static {
// 静的初期化子
staticValue = 20;
System.out.println("静的初期化子が呼び出されました");
}
public Example() {
System.out.println("コンストラクタが呼び出されました");
}
public static void main(String[] args) {
Example ex1 = new Example(); // 最初のオブジェクト生成
Example ex2 = new Example(); // 二番目のオブジェクト生成
}
}
//実行結果:
//静的初期化子が呼び出されました
//コンストラクタが呼び出されました
//コンストラクタが呼び出されました
この例では、Exampleクラスが初めて使用されたときに静的初期化子が一度だけ実行され、その後、オブジェクトが生成されるたびにコンストラクタが実行されることがわかります。
長々と説明しましたが、コンストラクタに関する例題を二つほど解いて理解を深めましょう。
次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Sample{ 2. static int num; 3. { 4. num = 10; 5. } 6. public Sample(){ 7. num = 100; 8. } 9. }
1. public class Main { 2. public static void main(String[] args){ 3. System.out.println(Sample.num); 4. } 5. }
A. 0が表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 10が表示される。
C. 100が表示される。
D. コンパイルエラーが発生する。
えぇと…「static int num;」はstaticな変数numってことなのにゃ。
次の{ num = 10; }の部分では初期化しているのにゃ。インスタンス初期化子はオブジェクト生成時に実行されるんだったかにゃ。
public Sample(){ num = 100; }の部分はコンストラクタだにゃ。だからSampleクラスのインスタンスが作られるときに実行されるんだにゃ。
で、Mainクラスを見るとSampleクラスのインスタンスを生成していないから、初期化ブロックやコンストラクタは実行されないのにゃ。
ということは、System.out.println(Sample.num);で実行されたときに呼び出されるのは「static int num;」のnumだから、初期値の0になると思うのにゃ。
つまり正解は選択肢Aだにゃ!
お見事です!
static変数はクラスがロードされたときに初期化され、その後プログラムがインスタンスを生成しない限り、その初期値が変わることはありません。このコードではSampleクラスのインスタンスを生成していないため、numの値は0
のままです。
ちなみに{ num = 10; }の部分を「static{ num = 10; }」とすれば実行時に10が表示されるようになります。
次のプログラムを実行し、コンパイルに「ok.」と表示したい。3行目の空欄に入るコードとして、正しいものを選びなさい。(1つ選択)
1. public class Sample { 2. public Sample() { 3. 「 空欄 」 4. } 5. public Sample(String str, int num){ 6. System.out.println("ok."); 7. } 8. }
1. public class Main{ 2. public static void main(String[] args) { 3. Sample s = new Sample(); 4. } 5. }
A. Sample(null, 0);
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. this(null, 0);
C. super(null, 0);
D. this.Sample(null, 0);
うわぁ、オーバーロードとの複合問題だにゃ。
オーバーロードされた別のコンストラクタから別のコンストラクタを呼び出すにはthisを使うんだったと思うんだけど…BとDのどっちかが分からないのにゃあ。
正解の選択肢はBです。Dでは何が問題なのか確認しましょう。
引数なしのコンストラクタであるpublic Sample()の中に書かれている「this.Sample(null, 0);」の行が問題です。
ここで何をしようとしているかというと、thisを使ってもう一つのコンストラクタであるpublic Sample(String str, int num)を呼び出そうとしていますが、この呼び出し方に間違いがあります。thisを使ってクラスのメソッドやフィールドにアクセスするときには、「.」
を使いますが、コンストラクタ呼び出しの場合はこれが許されていません。
Javaでは、コンストラクタ内で他のコンストラクタを呼び出す場合、特別な構文this(引数リスト);を使用する必要があります。この構文はJavaコンパイラに対して「同じクラスの他のコンストラクタを呼び出す」という特別な意味を持ちます。this.Sample(null, 0);と書くと、Javaコンパイラはそれを通常のメソッド呼び出しと解釈しようとし、コンストラクタ呼び出しとして認識できません。その結果、コンパイラは「Sampleという名前のメソッドが存在しない」と判断し、コンパイルエラーを発生させます。そのため選択肢Dは誤りとなります。
アクセス修飾子
アクセス修飾子は、クラスやメソッド、変数のアクセス範囲を制限するために使われます。
アクセス修飾子についてはJavaコース初級 基礎編Day13の回で紹介していますが、パブリック(public)とプライベート(private)以外にデフォルト(default:修飾子無し)とprotectedがあります。
アクセス修飾子の関係をまとめると以下のようになります。
修飾子 | 説明 |
---|---|
public | すべてのクラスからアクセス可能。 |
protected | 同じパッケージに属するか、継承しているサブクラスからのみアクセス可能 |
なし(デフォルト) | 同じパッケージに属するクラスからのみアクセス可能 |
private | クラス内からのみアクセス可能 |
protectedなんて修飾子あったっけ?
ほぼ使用したことがありませんね。
protectedは同じパッケージ内のクラス、またはサブクラス(継承したクラス)からアクセス可能な修飾子で、クラスを継承して使う場合に便利みたいです。
それでは、アクセス修飾子に関する例題を見てみましょう。
次のプログラムを確認してください。
1. package ex26; 2. 3. public class Parent { 4. int num = 10; 5. }
このクラス利用する以下のプログラムを、コンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. package other; 2. import ex26.Parent; 3. 4. public class Child extends Parent { 5. public static void main(String[] args) { 6. System.out.println(num); 7. } 8. }
A. 0が表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 10が表示される。
C. Childクラスの4行目でコンパイルエラーが発生する。
D. Childクラスの6行目でコンパイルエラーが発生する。
E. 実行時に例外がスローされる。
まず、ParentとChildは別のパッケージに属していて、ChildはParentを継承しているのにゃ。
Parentクラスのnumフィールドにはアクセス修飾子が付いていない、いわゆるデフォルト状態なのにゃ。デフォルト場合、同じパッケージ内でしかアクセスできないのにゃ。
Childクラスを見るとParentクラスのnumにアクセスしようとしているけど、ParentとChildは別のパッケージに属しているからアクセスできないのにゃ。
6行目でコンパイルエラーが発生するから正解は選択肢Dだにゃ!
正解です!
設問の場合、「protected int num = 10;」又は「public int num = 10;」と変更するとアクセス可能になります。
カプセル化とデータ隠蔽
カプセル化とは、プログラムで使うデータやメソッド(データを操作する手順)を一つの「カプセル」にまとめることです。カプセル化を使うと、プログラムが整理されて、使いやすくなります。
また、データ隠蔽とは、プログラムの内部で使っているデータを外から直接触れないように隠すことです。これによって、プログラムが壊れにくくなります。
例えば、ゲームのキャラクターをプログラムで作るとき、キャラクターの「名前」「体力」「攻撃力」などの情報があります。この情報を一つのカプセルにまとめて管理するのがカプセル化で、「キャラクターの体力」を勝手に外から変更されないようにすることがデータ隠蔽です。
public class Character {
private String name;
private int health;
private int attackPower;
public Character(String name, int health, int attackPower) {
this.name = name;
this.health = health;
this.attackPower = attackPower;
}
public String getName() {
return name;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
public int getAttackPower() {
return attackPower;
}
}
カプセル化とデータ隠蔽を行うことにより、例えば、キャラクターの体力をマイナスにするような間違いを防げます。また、キャラクターの情報を一つのクラスにまとめることで、どこに何があるかが分かりやすくなります。
デイトラJavaコース中級編と上級編の課題作成時にもカプセル化とデータ隠蔽を使用する場面は沢山ありました。
カプセル化とデータ隠蔽に関する例題を解いて、改めて理解を深めましょう。
次のプログラムを確認してください。
1. public class Sample { 2. int num; 3. int getNum() { return num;} 4. void setNum(int num){ this.num = num;} 5. }
このクラスにカプセル化を適用したい。次の中から正しいコードを選びなさい。(1つ選択)
A. public class Sample {
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
private int num;
private int getNum() { return num;}
private void set Num(int num) { this.num = num;} }
B. public class Sample{
public int num;
public int getNum() { return num;}
public void set Num(int num) { this.num = num;} }
C. public class Sample{
public int num;
private int getNum() { return num;}
private void set Num(int num) { this.num = num;} }
D. public class Sample{
private int num;
public int getNum() { return num;}
private void set Num(int num) { this.num = num;} }
まず、カプセル化したいんだったらフィールドをprivateにする必要があるから、BとCは誤りなのにゃ!あと、Aはゲッターをprivateにしていると他のクラスからアクセスできなくなるから間違いだにゃ。正解の選択肢はDだにゃ!
中級編と上級編をクリアーしているから、この辺は問題ないのにゃ!
流石ですね!
カプセル化とデータ隠蔽についてさらに深く理解するにはオブジェクト指向設計について学習する必要があります。JavaSilverの学習が完了したらオブジェクト指向設計について本格的に学んでもいいですね。
次回は「値渡しと参照渡し」について詳しく学習します
今回は「コンストラクタ」「アクセス修飾子」「カプセル化とデータ隠蔽」について解きました。
中級編の内容は記事にしていない部分が多く、カプセル化やデータ隠蔽については詳しく解説していませんでした。コードを書くときは散々使用しているのですが…
中級編の中でも文法やツールの解説回については今後記事化していこうかなと思っています。
以上で今回の学習記録を終えます。
ここまでご覧いただきありがとうございました。
コメント