こんにちは、運営者のハックです。
今回はデイトラJava Silver対策編「第7章の対策」のうち、中編の学習記録を紹介します。
引き続き「クラスの継承、インターフェース、抽象クラス」について学習!
第7章ではクラスの継承、インターフェース、抽象クラスについて学習しますが、中編の今回は「抽象メソッドの実装」「メソッドのオーバーライド」「ポリフォーフィズム」について解いていきます。
前編はこちら。
今回も引き続き解説の参考と例題の引用は【黒本】と呼ばれる「徹底攻略Java SE 11 Silver問題集」から行っていきます。
前回と同じく、今回の内容もJavaコース初級 基礎編Day13「クラス、インタフェース、メソッド、継承、多態性」の回で紹介している内容と重なっているから、併せて確認するのにゃ。
抽象メソッドの実装
前編でも説明しましたが、抽象メソッドは、具体的な処理内容を持たず、メソッドのシグネチャ(戻り値の型、メソッド名、引数のリスト)のみが定義されています。このため、抽象メソッドを含むクラスは抽象クラスとして定義されます。抽象クラス自体はインスタンス化(オブジェクトの生成)ができません。
抽象メソッドを含む抽象クラスを継承するサブクラスは、必ずこの抽象メソッドをオーバーライドして具体的な実装を提供しなければなりません。これは、抽象メソッドが持つ機能の具体的な処理を、サブクラスで定義することを強制するためです。ですから、抽象クラスは共通のインターフェース(操作や機能)を提供しつつ、具体的な動作をサブクラスに委ねることができます。
抽象クラスの実装について理解しやすいように、実際に例題を解いてみましょう。
次のプログラムを確認してください。
1. abstract class AbstractSample { 2. public void sample(){ 3. System.out.println("A"); 4. test(); 5. System.out.println("C"); 6. } 7. protected abstract void test(); 8. }
1. class ConcretSample extentds AbstractSample { 2. protected void test() { 3. System.out.println("B"); 4. } 5. }
このクラス利用する以下のプログラムを、コンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Main { 2. public static void main(String[] args) { 3. AbstractSample s = new ConcretSample(); 4. s.sample(); 5. } 6. }
A. 「A」「B」「C」と表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 「A」「C」と表示される。
C. AbstractSampleクラスでコンパイルエラーが発生する。
D. ConcreteSampleクラスでコンパイルエラーが発生する。
E. Mainクラスでコンパイルエラーが発生する。
F. 実行時に例外がスローされる。
えぇと…まず最初のコードは、AbstractSample という名前の抽象クラスを定義しているのにゃ。7行目は実装がなくて、具体的なサブクラスで実装される必要がある感じかにゃ。
次のコードはConcreteSample という名前の具体クラスを定義してて、AbstractSample 抽象クラスを継承しているのにゃ。ここまでのコードで特に変なところはないかにゃ。
最後のコードはMainクラスで、s.sample()を呼び出しているのにゃ。
s.sample()を呼び出すと、まず「A」が表示されるにゃ。次のtestメソッドで呼び出されるのは、ConcreteSample クラスで実装された test メソッドだから「B」出力されるのにゃ。最後に「C」が出力されるから、正解の選択肢はAだにゃ!
はい、正解です!
このように抽象クラスとそのメソッドをサブクラスでオーバーライドして実装する仕組みについて、理解しておきましょう。
メソッドのオーバーライド
オーバーライドは、スーパークラス(親クラス)のメソッドをサブクラス(子クラス)で再定義することです。親クラスのメソッドをそのまま使わずに、子クラスで独自の実装を提供したいときに使います。
オーバーライドについてはJavaコース初級 基礎編Day13「クラス、インタフェース、メソッド、継承、多態性」の回でも説明していますが、今回はさらに深掘りして解説します。
オーバーライドのルール
- メソッド名と引数が同じであること:親クラスと子クラスのメソッド名、引数の数と型が同じである必要があります。
- 戻り値の型が同じか、親クラスのメソッドの戻り値の型のサブクラスであること:子クラスのメソッドの戻り値の型は、親クラスのメソッドと同じか、それよりも特定の型(親クラスの型のサブクラス)でなければなりません。
- アクセス修飾子の範囲が広がっていること:子クラスのメソッドのアクセス修飾子は、親クラスのメソッドよりも厳しくすることはできません。例えば、親クラスがpublicなら、子クラスもpublicにする必要があります。
- 親クラスのメソッドに付いている例外を投げるか、それよりも少ない例外を投げること:子クラスのメソッドは、親クラスのメソッドで宣言されている例外か、それよりも少ない数の例外しか投げられません。
同名のフィールドがあった場合の優先度
ところで、継承関係にある2つのクラスで同名のフィールドが使用されている場合、どちらが優先されるのでしょうか?
実際の例題を解きながら解説していきます!
次のプログラムを確認してください。
1. class A { 2. String val = "A"; 3. void print() { 4. System.out.println(val); 5. } 6. } 7. 8. class B extends A { 9. String val = "B"; 10. }
これらのクラス利用する以下のプログラムを、コンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public class Main { 2. public static void main(String[] args) { 3. A a = new A(); 4. A b = new B(); 5. System.out.println(a.val); 6. System.out.println(b.val); 7. a.print(); 8. b.print(); 9. } 10. }
A. 「ABAB」と表示される。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. 「AAAA」と表示される。
C. 「AAAB」と表示される。
D. Bクラスでコンパイルエラーが発生する。
E. Mainクラスでコンパイルエラーが発生する。
F. 実行時に例外がスローされる。
あにゃにゃ、クラスAとクラスBにそれぞれ変数valのフィールドがあるのにゃ。
うーん…エラーは起きないと思うんだけど、どっちのフィールドが使用されるのかどうやって判断したらいいのにゃ?
継承関係にある2つのクラスで同名のフィールドが使用されている場合、どちらが優先されるかは、次のようなルールがあります。
継承関係にある2つのクラスで同名のフィールドが使用されている場合
- フィールドを参照した場合には、変数の型で宣言されたほうを使う。
- メソッドを呼び出した場合には、メソッド内の指示に従う。
以上のルールを踏まえて設問のコードを見てみましょう!
フィールドの参照(a.val,b.val)
- System.out.println(a.val);:
aはクラスAのオブジェクトです。a.valはクラスAのフィールドvalを参照するため、出力は”A”です。 - System.out.println(b.val);:
bはクラスBのオブジェクトですが、型はクラスAです。フィールドvalはコンパイル時に決定されるため、b.valはクラスAのvalを参照し、出力は”A”です。
メソッドの呼び出し(a.print(),b.print())
- a.print();:
aはクラスAのオブジェクトであり、クラスAのprint()メソッドが呼ばれます。このメソッドはa.valを表示するため、出力は”A”です。 - b.print();:
bはクラスBのオブジェクトですが、型はクラスAです。メソッドは実行時に決定されるため、クラスBのprint()メソッドが呼ばれます。ただし、print()メソッド自体はクラスAのものを使い、valはクラスAのフィールドを参照するため、出力は”A”です。
従って正解は選択肢Bです。
余談ですがChatGPTは選択肢Cを選びました。
ChatGPTも間違えるくらい難しいのかにゃ。
ポリフォーフィズム(多態性・多様性)
ポリモーフィズムとは、オブジェクト指向プログラミングにおいて、同じインターフェースや親クラスから派生した複数のオブジェクトが、同じメソッドを呼び出しても、それぞれ固有の振る舞いをする能力のことです。
ポリフォーフィズムについてはJavaコース初級 基礎編Day13「クラス、インタフェース、メソッド、継承、多態性」の回でも簡単に説明していますが、今回はさらに深掘りして解説します。
ルールと例
親クラスと子クラスの関係
親クラス(またはインターフェース)に定義されたメソッドを、子クラスでオーバーライドすることで、異なる動作を実装できます。
class Animal {
void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
親クラスの型で子クラスのインスタンスを持つ
親クラスの型で子クラスのオブジェクトを扱うことができます。これにより、同じ型の変数で異なる動作をするオブジェクトを扱えます。
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // 出力: Bark
myCat.makeSound(); // 出力: Meow
同じメソッド名で異なる動作
ポリモーフィズムにより、同じメソッド名が異なるクラスで異なる動作をすることができます。これにより、コードの再利用性が高まり、柔軟性が増します。
class Parent {
void greet() {
System.out.println("Hello");
}
}
class Child extends Parent {
@Override
void greet() {
System.out.println("Hi");
}
}
Parent p = new Parent();
Child c = new Child();
p.greet(); // 出力: Hello
c.greet(); // 出力: Hi
ポリフォーフィズムについて理解を深めるため、例題を解いて確認しましょう。
次のプログラムを確認し、これらのクラスとインターフェースを利用するMainクラスをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)
1. public interface Worker { 2. void work(); 3. }
1. class Employee implements Worker { 2. public void work(){ 3. System.out.println("work"); 4. } 5. public void report(){ 6. System.out.println("report"); 7. } 8. }
1. class Engineer extends Employee { 2. public void create() { 3. System.out.println("create future"); 4. } 5. }
1. public class Main { 2. public static void main(String[] args) { 3. Worker a = new Engineer(); 4. Employee b = new Engineer(); 5. Engineer c = new Engineer(); 6. a.create(); 7. b.work(); 8. c.report(); 9. } 10. }
A. Mainクラスの6行目でコンパイルエラーが発生する。
「徹底攻略 Java SE 11 Silver 問題集」より抜粋
B. Mainクラスの7行目でコンパイルエラーが発生する。
C. Mainクラスの8行目でコンパイルエラーが発生する。
D. 選択肢AとBの両方。
E. 選択肢BとCの両方。
えぇ…エラーが前提の問題なのにゃ?
「a.create();」「 b.work();」「c.report();」のどれか一つか二つにエラーが出るってことだよね?それぞれの場合で考えろってことなのにゃ。
では、それぞれのケースで考えてみましょう。
a.create();
aは Worker 型です。Worker 型は work() メソッドのみを持つことができます。
1. public interface Worker {
2. void work(); //ここに注目!
3. }
Worker a = new Engineer();
Workerインターフェースにはcreate() メソッドが定義されていないため、a.create(); の行でエラーが発生します。
b.work();
bは Employee 型です。Employee 型は work() と report() メソッドを持つことができます。
Employee b = new Engineer();
1. class Employee implements Worker {
2. public void work(){ //ここに注目
3. System.out.println("work");
4. }
5. public void report(){ //ここに注目
6. System.out.println("report");
7. }
8. }
Employee クラスに work() メソッドが定義されているため、b.work(); の行は問題ありません。
c.report();
cは Engineer 型です。Engineer 型は work(), report(), create() メソッドを持つことができます。
Engineer c = new Engineer();
1. class Engineer extends Employee { //ここに注目
2. public void create() { //ここに注目
3. System.out.println("create future");
4. }
5. }
Engineerクラスが Employee クラスを継承しているため report() メソッドが使用可能です。c.report(); の行も問題ありません。
従って正解の選択肢はAです。
この問題では、インターフェース型や親クラス型の変数を使ってオブジェクトを操作する際に、それぞれの型が持つメソッドしか呼び出せないことが重要なポイントでした。
ポリフォーフィズムを使った問題では、変数が何型かを確認するようにしましょう。
後編に続きます
今回は「クラスの継承、インターフェース、抽象クラス」について、「抽象メソッドの実装」「メソッドのオーバーライド」「ポリフォーフィズム」について解きました。
ところどころChatGPTも間違えるほど、内容が難しいと感じています。
IntelliJで実際にコードを入力しつつ、ChatGPTから正しい解説も聞きながら進めています。
そのおかげで、初級編で50%程度の理解度が今回の学習で75%程度には上がりそうです。
以上で今回の学習記録を終えます。
ここまでご覧いただきありがとうございました。
コメント