第11章 クラスの継承

この章では継承に関する基本的な考え方について説明します 。

HOMEPAGE

クラスの継承


クラスの継承とは、クラスが保持しているメンバやメソッドの内容を他のクラスで継承することを指します。

継承元のクラスのことを「スーパークラス」「親クラス」などと呼び、継承したクラスのことを「サブクラス」「子クラス」などと呼びます。親クラスを継承するためにはextendsを指定します。

class 親クラス名 {
    親クラスのメンバ変数;
    親クラスのメソッド;
}
class 子クラス名 extends 親クラス名 {
    super.親クラスのメンバ変数;
    super.親クラスのメソッド;
}
※「super.」は省略可。 但し、子クラスに親クラスと同じ名前のメンバ変数やメソッドがある場合は、明示的に「super.」を付加しないと自クラス(子クラス)のものが使用されます。



継承クラスの種類

他のクラスの要素を利用できる方法として、「継承」の他に「コンポジション」(集約)があります。

例:[JSample11_1.java]
package JSample;
class Animal {
      public void meow(){
        System.out.println("ニャー");
      }
    }

    class Name {
      public void printName(){
        System.out.println("タマネギ");
      }
    }

    class Cat extends Animal {
      public Name catName = new Name();
    }

    public class JSample11_1 {
      public static void main(String[] args) {
        Cat neko = new Cat();
        neko.meow();
        neko.catName.printName();
      }
    }
実行結果
ニャー
タマネギ


「親クラス」と「子クラス」のコンストラクタは次の規則で呼び出されます。

例:[JSample11_2.java]
package JSample;
class Oya {
    public String parentStr = "親クラスのメンバ変数が参照されました。";
    public Oya() {
        System.out.println("親クラスのコンストラクタ(引数なし)が呼ばれました。");
    }
    public void oyaMethod() {
        System.out.println("親クラスのメソッドが呼ばれました。");
    }
}
class Kodomo extends Oya { // 親クラスを継承。
    public String childStr = "子クラスのメンバ変数が参照されました。";
    public Kodomo() {
        System.out.println("自クラスのコンストラクタ(引数なし)が呼ばれました。");
    }
    public void childMethod() {
        System.out.println("子クラスのメソッドが呼ばれました。");
    }
}
public class JSample11_2 {
    public static void main(String[] args) {
        Kodomo child = new Kodomo(); // 子クラスのインスタンスを生成。
        System.out.println(child.parentStr); // 親クラスのメンバ変数を参照。
        System.out.println(child.childStr); // 子クラスのメンバ変数を参照。
        child.oyaMethod(); // 親クラスのメソッドの呼び出し。
        child.childMethod(); // 子クラスのメソッドの呼び出し。
    }
}
実行結果
親クラスのコンストラクタ(引数なし)が呼ばれました。← 子クラスのインスタンス生成時に親クラスのコンストラクタが先に呼ばれる。
自クラスのコンストラクタ(引数なし)が呼ばれました。
親クラスのメンバ変数が参照されました。← 子クラスから親クラスのメンバ変数を参照できている。
子クラスのメンバ変数が参照されました。
親クラスのメソッドが呼ばれました。← 子クラスから親クラスのメソッドを呼び出せている。
子クラスのメソッドが呼ばれました。


thisとは、呼び出されたコンストラクタやメソッドのオブジェクトを参照するために使われる予約語である。
thisを使ってメソッドの引数になっているローカル変数と、メンバ変数を区別することができます。

例:[JSample11_3.java]

class Animal {
    private String name = "タマネギ";

    public void printName(String name){
        System.out.println("ローカル変数 = " + name);
        System.out.println("メンバ変数 = " + this.name); 
    }
}
 
public class JSample11_3 {
    public static void main(String[] args) {
        String name = "ナス";
        Animal neko = new Animal();
        
        neko.printName(name);
    }
}
実行結果
ローカル変数 = ナス
メンバ変数 = タマネギ


superとは、Javaにおけるサブクラスでオーバーライド(親クラスのメソッドを子クラスで継承)された変数やインスタンスを参照する場合に使用されます。

つまり、子クラスのインスタンス(new [クラス名()]で作られたクラスの実体のこと)から、親クラスのインスタンスのメンバにアクセスして、値を参照する必要があるときにsuperが使用されます。

例:[JSample11_4.java]

package JSample;
class SuperClass {
    String str = "SuperClass";
    public String getStr() {
        return str;
    }
}
class SubClass extends SuperClass {
    String str = "SubClass";
    public String getStr() {
        return str;
    }
    public void print() {
        System.out.println("str = " + str);
        System.out.println("getStr() = " + getStr());
        System.out.println("super.str = " + super.str);
        System.out.println("super.getStr() = " + super.getStr());

    }
}
public class JSample11_4 {
    public static void main(String[] args) {
        SubClass sc = new SubClass();
        sc.print();
    }
}
実行結果
str = SubClass
getStr() = SubClass
super.str = SuperClass
super.getStr() = SuperClass


クラスを継承した時に元になっているスーパークラスで定義されているメソッドを継承したサブクラスにて同じメソッド名(と同じ引数)で書き換えることが出来ます。つまり上書きするということです。これをメソッドのオーバーライドと言います。

具体的な例で考えてみます。スーパークラスとしてクラスSuperClassを用意し、クラスSuperClassを継承したクラスSubClass1、クラスSubClass2があったとします。スーパークラスであるクラスAには「show」というメソッドが定義されています。ここでクラスB1で「show」というメソッドをオーバーライドしてみます。

例:[JSample11_5.java]

package JSample;
class SuperClass {
    public void show() {
        System.out.println("SuperClassのprint メソッド!");
    }
}
class SubClass1 extends SuperClass {
    public void show() {
        System.out.println("SubClassのprint メソッド!");
    }
}
class SubClass2 extends SuperClass {
}
class JSample11_5 {
    public static void main(String args[]) {
        B1 subc1 = new SubClass1();
        subc1.show();
        B2 subc2 = new SubClass2();
        subc1.show();
    }
}
実行結果
SubClassのprint メソッド!
SuperClassのprint メソッド!


メソッドを引数を付けて呼び出す時、引数に記述する値のデータ型はメソッドで決められたものしか指定できません。その為、同じような機能を持つメソッドであっても引数のデータ型が異なれば別々のメソッドを用意する必要があります。

Javaでは引数のデータ型や引数の数が完全に一致していなければ異なるメソッドに同じメソッド名を付けることが出来ます。

例:[JSample11_6.java]

package JSample;
class JSample11_6 {
    public static void main(String args[]) {
        System.out.println(plus(10, 7));
        System.out.println(plus(3.2, 4));
        System.out.println(plus(7, 1.223));
        System.out.println(plus(5.08, 2.4));
    }
    private static int plus(int n1, int n2) {
        System.out.println("int + int");
        return n1 + n2;
    }
    private static double plus(int n1, double d1) {
        System.out.println("int + double");
        return n1 + d1;
    }
    private static double plus(double d1, int n1) {
        System.out.println("double + int");
        return n1 + d1;
    }
    private static double plus(double d1, double d2) {
        System.out.println("double + double");
        return d1 + d2;
    }
}
実行結果
int + int
17 double + int
7.2
int + double
8.223
double + double
7.48


final修飾子はクラス、メソッド、変数の不変性を示して、対象の予期せぬ変更を防ぐために用います。
一度しか値を代入することができない変数を定数になったため、再代入が禁止になります。

class MyClass {
  void myMethod() {
    final int a = 0;
    a = 1;
  }
}
エラーメッセージ: pic here



クラスにfinalを付ける宣言することができる。そうすると、クラスを継承することができなくなる。
extendsに続く部分にfinalクラスが書いてあると、コンパイルエラーになる。
final class MyClass {
}

class MySubClass extends MyClass {
}
エラーメッセージ: pic here



メソッドにfinalを付けて宣言することができますが、メソッドにfinalを付けると、サブクラスでメソッドをオーバーライドできなくなります。
class MyClass {
  final public void myMethod() {
  }
}

class MySubClass extends MyClass {
  public void myMethod() {
  }
}
エラーメッセージ: pic here



円周率は既に「Math.PI」として定義されているが,finalのサンプルとしてあえて定義を独自に行っています。
円周率や面積を求めるメソッドは,定理に従ったものなのでこれ以上の追加はないものとしてfinalを付けます。
例:[JSample11_7.java]
package JSample;
class Circle {
    final double PI = 3.14159;//final変数PIの宣言、初期化
    final double getAreaOfCircle(double radius) {//finalメソッド宣言
        return PI * radius * radius;//円の面積
    }
}
public class JSample11_7 {
    public static void main(String[] args) {
        Circle circle = new Circle();//Circleクラスのオブジェクト作成
        double area = circle.getAreaOfCircle(2.0);//半径2.0の面積
        System.out.println("area = " + area);
    }
}
実行結果
area = 12.56636

練習

問題[JEx11_1]
従業員の管理者を表すManagerクラスを、Employeeクラスを継承してプログラムを完成しなさい
[Employee.java]

public class Employee {
    //処理
}
[Manager.java]
public class Manager {
    //処理
}
[JEx11_1.java]
public class JEx11_1 {
    public static void main(String[] args) {
        Employee taro = new Employee();
        sato.name = "佐藤";
        suzuki.name = "鈴木";

        Manager yamada = new Manager();
        yamada.name = "山田";

        //satoの持つoperationメソッドを呼び出します。
        //suzukiの持つoperationメソッドを呼び出します。
        //yamadaの持つoperationメソッドを呼び出します。
        //yamadaの持つmanagementメソッドを呼び出します。
    }
}
実行結果
佐藤は通常業務を行ないます。
鈴木は通常業務を行ないます。
山田は通常業務を行ないます。
山田は管理業務を行ないます。


問題[JEx11_2]
RPGに登場する魔法戦士を表すMagicFighterクラスを、Fighterクラスを継承して作成してプログラムを完成しなさい
[Fighter.java]

public class Fighter {
    //処理

    public Fighter() {
        //処理
    }

    public void attack() {
        //処理
    }
}
[MagicFighter.java]
public class MagicFighter extends Fighter {
    //処理

    public MagicFighter() {
        //処理
    }

    public void attack() {
        //処理
    }
}
[JEx11_2.java]
public class JEx11_2 {
    public static void main(String[] args) {
        Fighter fighter = new Fighter();
        fighter.attack();

        MagicFighter magicFighter = new MagicFighter();
        magicFighter.attack();
    }
}
実行結果
戦士の攻撃!
敵に10のダメージ!!
魔法戦士の魔法攻撃!
敵に30のダメージ!



戻る