KATOエンジニヤリング開発日誌

「アウトプット無きエンジニアにインプットもチャンスも無い」の精神で書いています

Javaの継承について学ぶ

「Javaプログラミング技法」の第9回目の授業内容まとめになります。

※前回授業の内容はこちら

www.kato-eng.info

継承とは

既に定義されているクラスを拡張して新しいクラスを作ることを継承という。拡張してできた新しいクラスでは既存クラスのフィールドやメソッドといったメンバを受け継ぐことができる。継承したクラスのことをサブクラス、継承された元のクラスをスーパークラスとも呼ぶ。

継承の方法

新しいクラスを元からあるクラスから継承するには「extends」キーワードを使用する。

修飾子 class サブクラス名 extends スーパークラス名 {
    // 新しいクラスのフィールド、コンストラクタ、メソッドについての記述
}
// 継承元のStudentクラスを拡張してAIITStudentクラスを作成する
public class AIITStudent extends Student {
    // 新しく追加したいメンバ
}

上記例の場合AIITStudentクラスはStudentクラスのフィールドやメソッドを継承している。但し、フィールドの場合、static宣言をしていないため、保持している値は共有ではない。

なぜ継承が使用されるのか

継承を使用しない場合、同じ処理を複数箇所に記載する事がある。

// Point2d.java
class Point2d {
    private int x;
    private int y;

    public Point2d() {
        x = 1;
        y = 2;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}
// Point3d.java
class Point3d {
    private int x;
    private int y;
    private int z;

    public Point3d() {
        x = 1;
        y = 2;
        z = 3;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getZ() {
        return z;
    }
}

上記例の場合、フィールドの「x」「y」とメソッドの「getX」「getY」がPoint2dクラスとPoint3dクラスで共通している。これを継承を使用して記載すると下記のようになる。

// Point3d.java
class Point3d extends Point2d {
    private int z;

    public Point3d() {
        z = 3;
    }

    public int getZ() {
        return z;
    }
}

継承を使用するとコードの記述量の減少だけでなく、コードの修正が発生した際にコードの修正量が減少することにもなる。

また、サブクラスのコンストラクタを呼ぶと、スーパークラスの引数無しのコンストラクタが最初に呼び出される。

// A.java
public class A {
    private int x;

    public A() {
        x = 1;
    }

    public int getX() {
        return x;
    }
}
// B.java
public class B extends A {
    private int y;

    public B() {
        // コードに記載されていないが、ここにクラスAの引数無しコンストラクタを実行する処理がある。
        y = 2;
    }

    public int getY() {
        return y;
    }
}
// Ex1.java
public class Ex1 {
    public static void main(String[] args) {
        Z aSample = new A();
        B bSample = new B();

        int xa = aSample.getX();
        // BクラスはAクラスを継承しているため、Aクラスの「getX」メソッドを呼び出すことができる
        int xb = bSample.getX();

        //AクラスはBクラスを継承していないため、Bクラスの「getY」メソッドを呼び出せない
        //int ya = aSample.getY();
        int yb = bSample.getY();

        System.out.println(xa);
        System.out.println(xb + ", " + yb);
    }
}

出力結果は下記のようになる。

1
1, 2

このことから、スーパークラスに引数無しコンストラクタが宣言されていないと、サブクラスをコンパイルするときにスーパークラスのコンストラクタを呼び出せずエラーになることがあることに注意する。

継承におけるアクセス制御

  • アクセス修飾子の種類
    • publicが付いたメンバ
      • 他のクラスからアクセスできる
    • privateが付いたメンバ
      • 他のクラスからアクセスできない
    • protectedが付いたメンバ
      • 自分のクラスとサブクラスからアクセスできる
// A.java
public class A {
    private int x;

    public A() {
        x = 1;
    }

    public int getX() {
        return x;
    }
}
// Ex2.java
public class Ex2 {
    public static void main(String[] args) {
        A aSample = new A();

        // Aクラスの「x」はprivateのため、Ex2クラスからはアクセスできない
        //aSample.x = 10;
        int xa = aSample.getX();
    }
}

Aクラスの変数「x」はprivateのため、Ex2クラスからはアクセスできないので上記のコメントアウトを外すとエラーが発生する。

public class B extends A {
    private int y;

    public B() {
        /*
        変数xはクラスAから継承しているが、
        スーパークラスのprivateメンバはサブクラスからでもアクセスができない。
        */
        //x = 10;
        y = 2;
    }

    public int getY() {
        return y;
    }
}
public class Ex2 {
    public static void main(String[] args) {
        B bSample = new B();

        int xb = bSample.getX();
    }
}

クラスBはクラスAを継承しているが、private変数であるクラスAのフィールド変数「x」には直接アクセスできない。

public class A {
    protected int x;

    public A() {
        x = 1;
    }

    public int getX() {
        return x;
    }
}
public class B extends A {
    private int y;

        public B() {
        // 変数xはクラスAのprotectedなのでサブクラスのクラスBからでもアクセスできる。
        x = 10;
        y = 2;
    }

    public int getY() {
    return y;
    }
} 

クラスBはクラスAを継承しており、クラスAのフィールド「x」はprotectedなのでクラスBからでもアクセスができる。

スーパークラスのコンストラクタ呼び出し

サブクラスのコンストラクタを呼ぶとスーパークラスの引数無しのコンストラクタが自動的に呼ばれるが、サブクラスから特定のスーパークラスのコンストラクタを明示的に呼びだすときには「super(…)」を使用する。

// A.java
public class A {
    private int x;

    public A() {
        x = 1;
    }

    public A(int x) {
        this.x = x;
    }

    public int getX() {
        return x;
    }
}
// B.java
public class B extends A {
    private int y;

    public B() {
        y = 2;
    }

    public B(int x, int y) {
        // Aクラスの引数1つのコンストラタクタを呼ぶ
        super(x);
        this.y = y;
    }

    public int getY() {
        return y;
    }
}
// Ex3.java
public class Ex3 {
    public static void main(String[] args) {
        A aSample = new A();
        // 引数2つのBクラスのコンストラクタを呼ぶ
        B bSample = new B(8, 9);

        int xa = aSample.getX();
        int xb = bSample.getX();
        int yb = bSample.getY();

        System.out.println("xa = " + xa);
        System.out.println("xb = " + xb);
        System.out.println("yb = " + yb);
    }
}

出力結果は下記になる。

xa = 1
xb = 8
yb = 9

スーパークラスのコンストラクタを呼び出す「super(…)」はサブクラスのコンストラクタ内の一番最初に実行する必要がある。一番最初以外に記述するとコンパイルエラーが発生する。

オーバーライド

スーパークラスで宣言されたメソッドをサブクラスで上書きすることをオーバーライドと呼ぶ。

// A.java
public class A {
    private int x;

    public A() {
        x = 1;
    }

    public String getStatus() {
        return "x: " + this.x;
    }
}
// B.java
public class B extends A {
    private int y;

    public B() {
        y = 2;
    }

    /*
        スーパークラスであるクラスAのメソッド「getStatus」を継承しているが、
        同名のメソッドgetStatusを再宣言している。
        「@Override」は記述しなくてもエラーにならないが、
        オーバーライドしていることを明示的に宣言しているので記述することが推奨されている。
    */
    @Override
    public String getStatus() {
        String tmp = super.getStatus();
        return tmp + ", y: " + this.y;
    }
}

多態性

スーパークラスの変数にはサブクラスのインスタンスを代入することができる。

// A.java
public class A {
    private int x;

    public A() {
        x = 1;
    }

    public A(int x) {
        this.x = x;
    }

    public int getX() {
        return x;
    }

    public String getStatus() {
        return "x: " + this.x;
    }
}
// B.java
public class B extends A {
    private int y;

    public B() {
        y = 2;
    }

    public B(int x, int y) {
        super(x);
        this.y = y;
    }

     public int getY() {
        return y;
    }

    @Override
    public String getStatus() {
        String tmp = super.getStatus();
        return tmp + ", y: " + this.y;
    }
}
public class Ex4 {
    public static void main(String[] args) {
        A aSample = new A();
        A bSample = new B(8, 9);

        int xa = aSample.getX();
        int xb = bSample.getX();
        // bSampleはA型の変数なのでgetYメソッドが呼べない
        //int yb = bSample.getY();

        System.out.println("xa = " + xa);
        System.out.println("xb = " + xb);

        String str1 = aSample.getStatus();
        System.out.println(str1);
        String str2 = bSample.getStatus();
        System.out.println(str2);
    }
}

上記例の出力結果は以下のようになる。

xa = 1
xb = 8
x: 1
x: 8, y: 9

bSampleはA型の変数であるが、そのインスタンスは本当の型であるBクラスのgetStatusメソッドが呼ばれている。スーパークラスの変数からメソッドを呼ぶと、インスタンスの実際の型に応じた処理が呼び出される(変数の型ではない)。このような仕組みを多態性と呼ぶ。

※次回授業の内容はこちら

www.kato-eng.info