【Java Silver対策】クラスの継承、インターフェース、抽象クラスについて学習しよう【例題にも挑戦!】後編

資格学習
ハック
ハック

こんにちは、運営者のハックです。

今回はデイトラJava Silver対策編「第7章の対策」のうち、後編の学習記録を紹介します。

引き続き「クラスの継承、インターフェース、抽象クラス」について学習!

第7章ではクラスの継承、インターフェース、抽象クラスについて学習しますが、後編の今回は「型の互換性、アップキャスト、ダウンキャスト」「thisの使用」「superの使用」について解いていきます。

前編はこちら。中編はこちら

今回も引き続き解説の参考と例題の引用は【黒本】と呼ばれる「徹底攻略Java SE 11 Silver問題集」から行っていきます。

ねこ奈
ねこ奈

前回と同じく、今回の内容もJavaコース初級 基礎編Day13「クラス、インタフェース、メソッド、継承、多態性」の回で紹介している内容と重なっているから、併せて確認するのにゃ。

型の互換性、アップキャスト、ダウンキャスト

型変換のアップキャストとダウンキャストは、プログラミングにおいてオブジェクトを異なる型に変換する方法です。簡単に言うと、アップキャストは広い範囲に変換することで、ダウンキャストは狭い範囲に変換することです。
まず基本的なクラスと継承の概念を説明し、その後にアップキャストとダウンキャストの例を示します。

基本概念

クラス: プログラムの設計図のようなもので、オブジェクト(実際のデータや動作)のテンプレート。

継承: あるクラス(親クラスまたはスーパークラス)の特徴を別のクラス(子クラスまたはサブクラス)が引き継ぐこと。子クラスは親クラスのデータやメソッドを使えるようになります。

class Animal {
    void eat() {
        System.out.println("This animal eats food.");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("The dog barks.");
    }
}

ここで、Dog クラスは Animal クラスを継承しています。つまり、Dog クラスは Animal クラスの eat() メソッドを使うことができます。

アップキャスト(Upcasting)

アップキャストとは、子クラスのオブジェクトを親クラスの型に変換することです。アップキャストは暗黙的に行われます(明示的なキャストは不要)。

Dog dog = new Dog();
Animal animal = dog; // アップキャスト
animal.eat(); // "This animal eats food." と表示されます

この例では、Dog 型のオブジェクトを Animal 型に変換しています。animal 変数は Dog オブジェクトを指しているため、animal.eat() は Dog クラスの eat() メソッドを呼び出します。

ダウンキャスト(Downcasting)

ダウンキャストとは、親クラスのオブジェクトを子クラスの型に変換することです。ダウンキャストは明示的に行う必要があり、キャスト演算子を使います

Animal animal = new Dog(); // アップキャスト
Dog dog = (Dog) animal; // ダウンキャスト
dog.bark(); // "The dog barks." と表示されます

この例では、Animal 型の変数 animal を Dog 型に変換しています。キャスト演算子 (Dog) を使って明示的にダウンキャストを行います。

注意点

アップキャストは安全で、失敗することはありません。子クラスのオブジェクトは常に親クラスの型に変換できます。ダウンキャストは注意が必要です。実際のオブジェクトがダウンキャストしようとする子クラスの型でない場合、ClassCastException が発生します。

Animal animal = new Animal();
Dog dog = (Dog) animal; // エラー: ClassCastException が発生します

この例では、animal 変数は Animal オブジェクトを指していますが、Dog 型にキャストしようとすると失敗します。

モナ
モナ

それではアップキャストとダウンキャストについて、実際に例題を解いてみましょう。

次のプログラムを確認してください。

1. class A {
2.    void hello(){
3.      System.out.println("A");
4.     }
5. }
1. class B extentds A {
2.     void hello() {
3.         System.out.println("B");
4.    }
5. }

これらのクラス利用する以下のプログラムを、コンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)

1. public class Main {
2.     public static void main(String[] args) {   
3.         A a = new A();
4.         B b = (B) a;        
5.         b.hello();  
6.     }
7. }         

A. Aが表示される。
B. Bが表示される。
C. Mainクラスでコンパイルエラーが発生する。
D. 実行時に例外がスローされる。

「徹底攻略 Java SE 11 Silver 問題集」より抜粋
ねこ奈
ねこ奈

…先ほど「注意点」の項目で説明していたコードのパターンとそっくりなのにゃ。

ということはClassCastExceptionが起きるから、正解の選択肢はDってことにゃ?

モナ
モナ

はい、その通りです!

このように型同士に互換性がない場合は、実行時に例外がスローされることに注意しましょう。

thisの使用

thisは、クラスのインスタンス自身を指す特別なキーワードです。主に次の2つの場面で使われます。

同じ名前のローカル変数とフィールドを区別するため

例えば、コンストラクタ内でフィールドに値を設定する場合は以下のとおりです。

class Dog {
    String name;
    int age;

    Dog(String name, int age) {
        this.name = name; // フィールドのnameにローカル変数nameを代入
        this.age = age;   // フィールドのageにローカル変数ageを代入
    }
}

ここでは、this.nameがフィールドのnameを指し、nameだけだとコンストラクタの引数のローカル変数nameを指します。thisを使うことで、フィールドとローカル変数を区別しています。

インスタンスメソッド内でインスタンスフィールドや他のメソッドにアクセスするため

インスタンスメソッド内で、thisを使ってフィールドやメソッドにアクセスできます。

class Dog {
    String name;
    int age;

    void printInfo() {
        System.out.println("Name: " + this.name); // thisを使ってフィールドにアクセス
        System.out.println("Age: " + this.age);
    }
}

ここで、printInfoメソッド内でthisを使ってnameとageにアクセスしています。thisを使わなくても、同じインスタンスのフィールドにアクセスできますが、thisを使うことで、コードが明確になります。

モナ
モナ

thisは今までの学習でも至る箇所で使用してきました。

そんなthisの使用に関する例題を解いてみましょう。

次のプログラムを確認してください。

1. class Parent {
2.    String name;
3.    String getName() {
4.        return this.name;
5.     }
1. public class Child extentds Parent {
2.     String name;
3. }

これらのクラス利用する以下のプログラムを、コンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)

1. public class Main {
2.     public static void main(String[] args) {   
3.         Child child = new Child();
4.         child.name = "sample";        
5.         System.out.println(child.getName());
6.     }
7. }         

A. 「Sample」と表示される。
B. 「null」と表示される。
C.  何も表示されない。
D. コンパイルエラーが発生する。
E. 実行時に例外がスローされる。

「徹底攻略 Java SE 11 Silver 問題集」より抜粋
ねこ奈
ねこ奈

親クラスと子クラスにnameという同じ名前のフィールドが定義されているのにゃ。

えーと、「child.name = “sample”;」で設定しているnameはChildクラスのフィールドのnameだよねぇ。

だけど、 「child.getName()」で呼び出しているのはParentクラスのgetNameメソッドだにゃ。getNameメソッド内の「return this.name;」は同じクラス内のnameフィールドを指しているのにゃ。

つまり、Parentクラスのnameフィールドには何も設定されていないから、返されるのはデフォルト値であるnullなのにゃ!正解の選択肢はBだにゃ!

モナ
モナ

ねこ奈お見事です!

子クラスに親クラスと同じ名前のフィールドがある場合、子クラスのフィールドが親クラスのフィールドを隠蔽します。しかし、メソッドの呼び出しにおいては、メソッドが定義されているクラスのフィールドが使用されます。この例では、getNameメソッドはParentクラスに定義されているため、Parentクラスのnameフィールドが参照されます。

superの使用

superは、子クラスから親クラスのフィールドやメソッドにアクセスするためのキーワードです。親クラスにあるフィールドやメソッドを、子クラスから使いたいときに使います。

以下の具体例を見て、thisとsuperの違いを理解しましょう。

class Parent {               //親クラス 
    String name = "Parent";
}
class Child extends Parent {  //子クラス
    String name = "Child";

    void printNames() {
        System.out.println("Child name: " + this.name); // 子クラスのフィールド
        System.out.println("Parent name: " + super.name); // 親クラスのフィールド
    }
}
public class Main {     //Mainクラス
    public static void main(String[] args) {
        Child child = new Child();
        child.printNames();
    }
}
//実行結果
Child name: Child
Parent name: Parent

このように、thisでアクセスすると子クラスのフィールドが使われ、superでアクセスすると親クラスのフィールドが使われます。

モナ
モナ

親クラスのものを使いたいときにsuperを使うということですね。

それでは、superについても実際に例題を解いてみましょう。

次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)

1. class Parent {
2.     public Parent() {
3.       System.out.println("A");
4.     }
5.     public Parent(String val){
6.         this();
7.         System.out.println(val);
8.     }
9. }
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
1. public class Main {
2.     public static void main(String[] args){
3.        new Child("D");
4.    }
5. }

A. 「A」「B」「C」「D」と表示される。
B. 「A」「B」と表示される。
C. 「B」「A」「D」「C」と表示される。
D. 「A」「B」「D」「C」と表示される。
E. コンパイルエラーが発生する。
F. 実行時に例外がスローされる。

「徹底攻略 Java SE 11 Silver 問題集」より抜粋
ねこ奈
ねこ奈

う、うわぁ…コンストラクタの呼び出し順が複雑になっているのにゃあ…

モナ
モナ

実行フローについて、順番に見ていきましょう!

  • new Child(“D”) により、Child クラスのコンストラクタ Child(String val) が呼ばれる。
1. public class Main {
2.     public static void main(String[] args){
3.        new Child("D");
4.    }
5. }
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Child(String val) コンストラクタでは、this() が呼び出され、Child() コンストラクタが呼び出される。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Child() コンストラクタでは、super(“B”) が呼び出され、親クラスの Parent(String val) コンストラクタが呼び出される。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
1. class Parent {
2.     public Parent() {
3.       System.out.println("A");
4.     }
5.     public Parent(String val){
6.         this();
7.         System.out.println(val);
8.     }
9. }
  • Parent(String val) コンストラクタでは、this() が呼び出され、Parent() コンストラクタが呼び出される。
1. class Parent {
2.     public Parent() {
3.       System.out.println("A");
4.     }
5.     public Parent(String val){
6.         this();
7.         System.out.println(val);
8.     }
9. }
  • Parent() コンストラクタが実行され、”A” が出力される。
1. class Parent {
2.     public Parent() {
3.       System.out.println("A");
4.     }
5.     public Parent(String val){
6.         this();
7.         System.out.println(val);
8.     }
9. }
  • Parent(String val) コンストラクタに戻り、”B” が出力される。
1. class Parent {
2.     public Parent() {
3.       System.out.println("A");
4.     }
5.     public Parent(String val){
6.         this();
7.         System.out.println(val);
8.     }
9. }
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Parent(String val) コンストラクタが終了し、Child() コンストラクタに戻る。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Child() コンストラクタが実行され、”C” が出力される。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Child() コンストラクタが終了し、Child(String val) コンストラクタに戻る。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
  • Child(String val) コンストラクタが実行され、”D” が出力される。
1. class Child extends  Parent {
2.     public Child() {
3.         super("B");
4.         System.out.println("C");
5.     }
6.     public Child(String val){
7.         this();
8.         System.out.println(val);
9.     }
10. }
1. public class Main {
2.     public static void main(String[] args){
3.        new Child("D");
4.    }
5. }
モナ
モナ

従って「A」「B」「C」「D」が表示されるため、正解の選択肢はAです。

複雑なコードの場合は実行順番を追って確実に問題を解くようにしましょう。

ねこ奈
ねこ奈

ここまで複雑で問題を解くのに時間がかかるんだったら、問題を飛ばして先に進んだほうが良い気がするのにゃ…

まとめ 第7章の理解は現役エンジニアの方でも重要

今回は「クラスの継承、インターフェース、抽象クラス」について、「型の互換性、アップキャスト、ダウンキャスト」「thisの使用」「superの使用」について解きました。

ハック
ハック

第7章の学習は理解するのが難しく、時間がかかってしまいました。

現場に出る上でクラスの継承などをきちんと理解しておく必要があります。

この分野は何度も復習を繰り返して理解を深めるようにしたいです。

以上で今回の学習記録を終えます。

ここまでご覧いただきありがとうございました。

コメント

タイトルとURLをコピーしました