最終更新日:2021/01/30 原本2017-

クラスの継承

JavaでもC++と同様にクラスを継承によって開発の効率化や安全性の向上が可能です.
アプリケーションの作成する時に,一から全て作ろうとすると大変です. 多くの場合,よく使う機能などを「モジュール」(似たようなものとして「コンポーネント」や「ライブラリ」もあり,場合によっては同義であったりします)と呼ばれる関数などを使って構造化しておいてそれを再利用する事が行われます. 標準入出力やファイルアクセスなどの基本的なものでなく,ネットワークやグラフィックでの処理などもモジュール化されたものが標準的に利用できます. Javaでもたくさんのモジュールが利用可能ですので,Java APIを使いこなせるようになっておいて下さい. そんな時,「既にあるモジュールを少し改良したい」と思うこともあるかもしれません. もちろん,自分でもモジュールを作成することが出来ます. 既にあるモジュールを改良する事を考えた時,既にあるモジュールのソースコードをコピーして,改良部分を追加するのもいいかもしれません. でも,そのようなことを繰り返していると,似たようなモジュールがたくさん出来てしまい,もし,元のモジュールにミスがあった時.修正する事が非常に難しくなってしまう場合もあります. コピーによって作られたモジュールのどこがオリジナルでどこが改変された部分なのかの情報をきちんとコメントとして残しておいても,それを探す事が難しいです.

このような先人の経験にもとづき,オブジェクト指向プログラミングが開発されました. Javaもオブジェクト指向プログラミングの一つです. オブジェクトと指向プログラミングでは「継承」という技術によって,先に述べた開発の効率化が可能になります.
それでは,クラスの継承の例を見ていきましょう. ここでは,既に作成した(2次元での)Coordinateクラスを継承して,3次元でのクラス点を作成してみましょう. Coordinateではx座標とy座標のフィールド変数を持っていました. 3次元でのクラス点では更にz座標が必要になってきます. Coordinateクラスをコピーして,ソースを追加する形で新しいクラスを作成しても良いのですが,先程のような開発の効率が悪くなってしまう可能性があります. そこで,Coordinateクラスを継承して,z座標のみを加える形で新しいCoordinate3Dクラスを作成してみましょう.

Coordinate3D.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package jp.ac.utsunomiya_u.is;
 
public class Coordinate3D extends Coordinate {
 
    /**
     * Z座標
     */
    private double z = 0.0;
 
    /**
     * コンストラクタ(デフォルト)
     */
    Coordinate3D() {
        super();
        z = 0.0;
    }
 
    /**
     * コンストラクタ
     *
     * @param x X座標
     * @param y Y座標
     * @param z Z座標
     */
    Coordinate3D(double x, double y, double z) {
        super(x, y);
        this.z = z;
    }
 
    /**
     * コンストラクタ(コピー)
     *
     * @param coordinate3D 3次元座標インスタンス
     */
    Coordinate3D(Coordinate3D coordinate3D) {
        super(coordinate3D.getX(), coordinate3D.getY());
        this.z = coordinate3D.z;
    }
 
    /**
     * セッタ for Z座標
     *
     * @param z Z座標
     */
    void setZ(double z) {
        this.z = z;
    }
 
    /**
     * セッタ for X-Y-Z座標
     *
     * @param x X座標
     * @param y Y座標
     * @param z Z座標
     */
    void set(double x, double y, double z) {
        super.set(x, y);
        this.z = z;
    }
 
    /**
     * ゲッタ for Z座標
     *
     * @return Z座標
     */
    double getZ() {
        return z;
    }
}
8行目
z座標のためのフィールド変数をdouble型で用意しておきましょう. フィールド変数はやはりprivateにしておきましょう.
13-16行目
ディフォルトコンストラクタで,x座標,y座標,z座標を初期化しています.
superはスーパークラス(親クラスとも呼ばれます),すなわちCoordinateクラスを指します. super()とすると,スーパークラスのディフォルトコンストラクタが呼ばれます.
superでスーパークラスのコンストラクタを呼ぶときは,先頭行に書かなければなりません. (先頭行に書かないとコンパイルエラーになります.)
25-28行目
コンストラクタで,与えられたx座標,y座標,z座標を初期化しています.
super(x,y)とすると,スーパークラスのコンストラクタのうち,double型を2つ引数にもつコンストラクタが呼ばれます.
35-38行目
コピーコンストラクタで,x座標,y座標,z座標を初期化しています.
45-47行目
z座標のためのセッタ
56-59行目
x座標,y座標,z座標を一括で設定するセッタです.
superで指し示されるスーパークラスのsetメソッドでx座標とy座標を設定しています.
66-68行目
z座標のためのゲッタ
この継承において,スーパークラスは一切変更していません.
Coordinate3D.javaと以下のCoordinate3DTest.javaをコピーして動作を確認してください.

Coordinate3DTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package jp.ac.utsunomiya_u.is;
 
public class Coordinate3DTest {
 
    public static void main(String[] args) {
        Coordinate3D coordinate3D = new Coordinate3D(1, 2, 3);
        System.out.println("x = " + coordinate3D.getX() + "  y = " + coordinate3D.getY() + " z = " + coordinate3D.getZ());
        coordinate3D.set(4, 5, 6);
        System.out.println("x = " + coordinate3D.getX() + "  y = " + coordinate3D.getY() + " z = " + coordinate3D.getZ());
        coordinate3D.setX(7);
        coordinate3D.setY(8);
        coordinate3D.setZ(9);
        System.out.println("x = " + coordinate3D.getX() + "  y = " + coordinate3D.getY() + " z = " + coordinate3D.getZ());
    }
}

出力

x = 1.0  y = 2.0 z = 3.0
x = 4.0  y = 5.0 z = 6.0
x = 7.0  y = 8.0 z = 9.0
7行目
x, y, z座標を標準出力に出力しています.
getXとgetYメソッドはCoordinate3Dにはありませんが,継承しているので,スーパークラスのメソッドもサブクラス(子クラスとも呼ばれます)のインスタンスで呼ぶことが出来ます.
10-12行目
x, y, z座標を再設定しています.
setXとsetYメソッドはCoordinate3Dにはありませんが,継承しているので,スーパークラスのメソッドもサブクラスのインスタンスで呼ぶことが出来ます.

C++との違い

C++のように多重継承は出来ません
Javaでは1つのクラスのスーパークラスはただ1つしかもてません. これを単一継承と呼びます.

1
class ChildClass extends FatherClass, MotherClass
とすることは出来ません.
C++のように継承の際にアクセス制御はありません

1
class Coordinate3D extends public Coordinate
のように書けません.
クラス名にfinalをつけると,そのクラスは継承できません

1
final class Coordinate3D extends Coordinate
とするとCoordinate3Dクラスを継承したサブクラスは定義出来ません. 継承させたくないクラスの場合,final修飾子をつけましょう. 例えば,文字列を扱うクラスStringクラスはfinal修飾子がついていいるので,継承して新しいクラスを作ることは出来ません.

オーバーライド

前回のオーバーロードとは異なるので注意して下さい.
クラスの継承において,これまで見てきたような機能の追加(フィールドやメソッドの追加)だけでなく,機能の更新も可能です. 具体的には,スーパークラスで既に定義されているメソッドの働きをそれを継承したサブクラスで変更することができます. 簡単な例を見てみましょう.

Vehicle.java

1
2
3
4
5
6
7
8
package jp.ac.utsunomiya_u.is;
 
public class Vehicle {
 
    void printEnergySource() {
        System.out.println("I am powered by gasoline.");
    }
}
VehicleクラスはprintEnergySourceというメソッドを持っています(他は省略しています). これをスーパークラスとして継承したElectricVehicleクラスを次のようにしてみましょう.

ElectricVehicle.java

1
2
3
4
5
6
7
8
9
package jp.ac.utsunomiya_u.is;
 
public class ElectricVehicle extends Vehicle {
 
    @Override
    void printEnergySource() {
        System.out.println("I am powered by electricity.");
    }
}
ElectricVehicleは既にあるVehicleを継承したいのですが,printEnergySourceメソッドの名前や引数を変えずに動作を変更したいとします. このような場合,スーパークラスと同一名,同一引数(この例では引数はありません)でメソッドを再定義出来ます. これを「オーバーライド」と言います.
@Overrideは「アノテーション」と呼ばれるJavaの注釈機能です. 標準アノテーションとしては
表記意味
@Deprecated非推奨扱い
@Overrideオーバーライド
@SuppressWarnings警告メッセージ抑制
があります. 自分で作成することも可能です. アノテーションは単に人間が理解するためののコメントとしての意味だけなく,コンパイラもその意図を理解して挙動します. 例えば,@Overrideを付けてオーバーライドしたつもりでいても,メソッド名が違っていた場合,コンパイラエラーとなります. アノテーションをつける事は必須ではありませんが,オーバーライドした時にはなるべく@Overrideをつけるようにしましょう.
同じように,ElectricVehicleクラスを継承したPlugInHybridVehicleクラスも以下のように出来ます.

PlugInHybridVehicle.java

1
2
3
4
5
6
7
8
9
package jp.ac.utsunomiya_u.is;
 
public class PlugInHybridVehicle extends ElectricVehicle {
 
    @Override
    void printEnergySource() {
        System.out.println("I am powered by galoline and electricity.");
    }
}

VehicleTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package jp.ac.utsunomiya_u.is;
 
public class VehicleTest {
 
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.printEnergySource();
        ElectricVehicle electricVehicle = new ElectricVehicle();
        electricVehicle.printEnergySource();
        PlugInHybridVehicle plugInHybridVehicle = new PlugInHybridVehicle();
        plugInHybridVehicle.printEnergySource();
    }
}
それぞれのインスタンスでprintEnergySourceを呼んでみると以下のような出力を得ます.

出力

I am powered by gasoline.
I am powered by electricity.
I am powered by galoline and electricity.
Vehicle.java, ElectricVehicle.java, PlugInHybridVehicle.java, VehicleTest.javaをコピーして実行し,動作を確認してみましょう.

final修飾子が付いたメソッド

メソッドにfinal修飾子をつけることが出来ます. finalがついたメソッドはオーバーライド出来ません. finalがついたクラスは継承が出来ませんでしたが,継承はしても良いが,特定のメソッドはオーバーライドして欲しくない場合に使えます.

Objectクラスのメソッドのオーバーライド

Coordinate3DTestのmainメソッドの最後に

Coordinate3DTest.javaへの追加部分

1
System.out.println(coordinate3D.toString());
を追加して,動作を確認してみましょう.

出力

jp.ac.utsunomiya_u.is.Coordinate3D@15db9742
このtoStringメソッドはObjectクラスのメソッドです. あらゆるクラスはObjectクラスを継承しているので,必ずtoStringが利用できます. toStringはオブジェクトの文字列を返しますが,ハッシュコードが返されるので,あまり有用ではありません. そこで,toStringをオーバーライドして,有用なtoStringを作ってみましょう.
Coordinate3D.javaに以下を追加し,動作を確認してみましょう.

Coordinate3D.javaへの追加部分

1
2
3
4
5
6
7
8
9
/**
  * toStringのオーバーライド
  *
  * @return X-Y-Z座標の文字列
  */
@Override
public String toString() {
    return "(" + getX() + ", " + getY() + ", " + getZ() + ")";
}

出力

(7.0, 8.0, 9.0)
自作クラスではその意図にあったtoStringをオーバーライドする事が推奨されます. 必須ではありませんが,なるべく適切なtoStringをオーバーライドしましょう.

インナークラス

Javaではカプセル化によりメソッドやフィールドへのアクセス制限を実現し,開発の安全性を高めることが出来ます. また,クラス自体へのアクセス修飾子はアクセス修飾子なしの”同一パッケージ内からのみアクセス可能”か,publicの”どこからでもアクセス可能”の2つだけです. しかし,自分専用のクラス構造を定義したい場合もあります. このような場合,private修飾子をクラスにつけることが思いつきますが,privateでは誰からも(自分さえ)アクセスできない無意味なクラスとなってしまいます. このような要求に対して,Javaではインナークラスが利用できます. インナークラスはクラス内に定義されているクラスです.
VehicleTest.javaに以下を追加し,動作を確認してみましょう.

VehicleTest.javaへの追加部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package jp.ac.utsunomiya_u.is;
 
public class VehicleTest {
 
    static class NaturalGasVehicle extends Vehicle {
 
        @Override
        void printEnergySource() {
            System.out.println("I am powered by natural gas.");
        }
    }
 
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.printEnergySource();
        ElectricVehicle electricVehicle = new ElectricVehicle();
        electricVehicle.printEnergySource();
        PlugInHybridVehicle plugInHybridVehicle = new PlugInHybridVehicle();
        plugInHybridVehicle.printEnergySource();
        NaturalGasVehicle naturalGasVehicle = new NaturalGasVehicle();
        naturalGasVehicle.printEnergySource();              
    }
}
メンバー
「フィールド」,「メソッド」,「インナークラス」の3つを合わせてクラスのメンバーと呼びます. インナークラスの事をメンバークラスと呼ぶこともあります.
以下のInnerClassTest.javaとOuterClassTest.javaをコピーし,動作を確認してみましょう.

InnerClassTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package jp.ac.utsunomiya_u.is;
 
import jp.ac.utsunomiya_u.is.test.OuterClassTest;
 
public class InnerClassTest {
 
    InnerClassTest() {
        new InnerClassPrivate();
        new InnerClassPackagePrivate();
        new InnerClassProtected();
        new InnerClassPublic();
        new InnerClassPrivateStatic();
        new InnerClassPackagePrivateStatic();
        new InnerClassProtectedStatic();
        new InnerClassPublicStatic();
 
        //new OuterClassTest.InnerClassPrivate(); // privateメンバーにクラス外部からアクセスできません
        //new OuterClassTest.InnerClassPackagePrivate(); // publicでないメンバーにパッケージ外部からアクセスできません
        //new OuterClassTest.InnerClassProtected(); // publicでないメンバーにパッケージ外部からアクセスできません
        //new OuterClassTest.InnerClassPublic(); // 非staticメンバーのインナークラスをインスタンス化するためには,一度アウタークラスのインスタンスを生成しなければなりません
        OuterClassTest outerClassTest = new OuterClassTest();
        OuterClassTest.InnerClassPublic innerClassPublic = outerClassTest.new InnerClassPublic();
        //new OuterClassTest.InnerClassPrivateStatic(); // privateメンバーにクラス外部からアクセスできません
        //new OuterClassTest.InnerClassPackagePrivateStatic(); // publicでないメンバーにパッケージ外部からアクセスできません
        //new OuterClassTest.InnerClassProtectedStatic(); // publicでないメンバーにパッケージ外部からアクセスできません
        new OuterClassTest.InnerClassPublicStatic();
 
    }
 
    private class InnerClassPrivate {
 
        InnerClassPrivate() {
            System.out.println(getClass().getName());
        }
    };
 
    class InnerClassPackagePrivate {
 
        InnerClassPackagePrivate() {
            System.out.println(getClass().getName());
        }
    };
 
    protected class InnerClassProtected {
 
        InnerClassProtected() {
            System.out.println(getClass().getName());
        }
    }
 
    public class InnerClassPublic {
 
        public InnerClassPublic() {
            System.out.println(getClass().getName());
        }
    }
 
    private static class InnerClassPrivateStatic {
 
        InnerClassPrivateStatic() {
            System.out.println(getClass().getName());
        }
    };
 
    static class InnerClassPackagePrivateStatic {
 
        InnerClassPackagePrivateStatic() {
            System.out.println(getClass().getName());
        }
    };
 
    protected static class InnerClassProtectedStatic {
 
        InnerClassProtectedStatic() {
            System.out.println(getClass().getName());
        }
    }
 
    public static class InnerClassPublicStatic {
 
        public InnerClassPublicStatic() {
            System.out.println(getClass().getName());
        }
    }
 
    public static void main(String[] args) {
        System.out.println("##### Inner Class Test #####");
        new InnerClassTest();
        System.out.println("##### Outer Class Test #####");
        new OuterClassTest();
 
        System.out.println("##### Static Class Test #####");
        //new InnerClassPrivate(); // staticメソッドから非staticメンバーにアクセスできません
        //new InnerClassPackagePrivate(); // staticメソッドから非staticメンバーにアクセスできません
        //new InnerClassProtected(); // staticメソッドから非staticメンバーにアクセスできません
        //new InnerClassPublic(); // staticメソッドから非staticメンバーにアクセスできません
        new InnerClassPrivateStatic();
        new InnerClassPackagePrivateStatic();
        new InnerClassProtectedStatic();
        new InnerClassPublicStatic();
 
        System.out.println("##### Local Class Test #####");
        int i = 0;
        final int f = 0;
        class LocalClass {
 
            public LocalClass() {
                //i = 1; // ローカルクラス内からアウターメソッドのローカル変数にアクセスすることは出来ません
                System.out.println(f);
                System.out.println(getClass().getName());
            }
        }
 
        new LocalClass();
 
    }
}

OuterClassTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package jp.ac.utsunomiya_u.is.test;
 
public class OuterClassTest {
 
    private int i = 0;
    private static int s = 0;
 
    public OuterClassTest() {
        new InnerClassPrivate();
        new InnerClassPackagePrivate();
        new InnerClassProtected();
        new InnerClassPublic();
        new InnerClassPrivateStatic();
        new InnerClassPackagePrivateStatic();
        new InnerClassProtectedStatic();
        new InnerClassPublicStatic();
    }
 
    private class InnerClassPrivate {
 
        //private static int ss =0; // staticフィールドは利用できません
        InnerClassPrivate() {
            i = 1;
            s = 1;
            System.out.println(getClass().getName());
        }
    };
 
    class InnerClassPackagePrivate {
 
        InnerClassPackagePrivate() {
            i = 1;
            s = 1;
            System.out.println(getClass().getName());
        }
    };
 
    protected class InnerClassProtected {
 
        InnerClassProtected() {
            i = 1;
            s = 1;
            System.out.println(getClass().getName());
        }
    }
 
    public class InnerClassPublic {
 
        public InnerClassPublic() {
            i = 1;
            s = 1;
            System.out.println(getClass().getName());
        }
    }
 
    private static class InnerClassPrivateStatic {
 
        private static int ss = 0;
 
        InnerClassPrivateStatic() {
            //i = 1; // 非staticメンバークラスはアウタークラスの非staticフィールドにアクセスできません
            s = 1;
            System.out.println(getClass().getName());
        }
    };
 
    static class InnerClassPackagePrivateStatic {
 
        InnerClassPackagePrivateStatic() {
            //i = 1; // 非staticメンバークラスはアウタークラスの非staticフィールドにアクセスできません
            s = 1;
            System.out.println(getClass().getName());
        }
    };
 
    protected static class InnerClassProtectedStatic {
 
        InnerClassProtectedStatic() {
            //i = 1; // 非staticメンバークラスはアウタークラスの非staticフィールドにアクセスできません
            s = 1;
            System.out.println(getClass().getName());
        }
    }
 
    public static class InnerClassPublicStatic {
 
        public InnerClassPublicStatic() {
            //i = 1; // 非staticメンバークラスはアウタークラスの非staticフィールドにアクセスできません
            s = 1;
            System.out.println(getClass().getName());
        }
    }
}
インナークラスは以下の4つに分類されます.
staticメンバークラスアウタークラスのインスタンスを生成しなくいてもインスタンスを生成出来ます.staticメンバーにアクセスできます.
非staticメンバークラスアウタークラスをインスタンス化しないとインスタンスを生成出来ません.staticメンバーにアクセスできません.
ローカルクラスメソッド内で定義されます.ローカル変数がそうであるように,static修飾子とアクセス修飾子(private, protected, public)をつけることはできません.
匿名クラス次節参照

匿名クラス

一時的に,既に存在するクラスに少し変更を加えたクラスを使用したい場合があります. このような場合,既に存在するクラスを継承したサブクラスを新しいファイルに書かなければなりませんが,少し大げさと思える場合もあります. こんな時,匿名クラス(無名クラスとも呼ばれます)と呼ばれるクラスを利用できます. 匿名クラスは,一時的な使い捨てのサブクラスのようなもので,インスタンスを生成する際に,クラスを継承する形でサブクラスを定義出来ます.
VehicleTest.javaに以下を追加し,動作を確認してみましょう.

VehicleTest.javaへの追加部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package jp.ac.utsunomiya_u.is;
 
public class VehicleTest {
 
    static class NaturalGasVehicle extends Vehicle {
 
        @Override
        void printEnergySource() {
            System.out.println("I am powered by natural gas.");
        }
    }
 
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.printEnergySource();
        ElectricVehicle electricVehicle = new ElectricVehicle();
        electricVehicle.printEnergySource();
        PlugInHybridVehicle plugInHybridVehicle = new PlugInHybridVehicle();
        plugInHybridVehicle.printEnergySource();
        NaturalGasVehicle naturalGasVehicle = new NaturalGasVehicle();
        naturalGasVehicle.printEnergySource();
         
        Vehicle vehicle2 = new Vehicle() {
            @Override
            void printEnergySource() {
                System.out.println("I am powered by water.");
            }
        };
        vehicle2.printEnergySource();
    }
}
上の例では,スーパークラスのVehicleクラスを継承して,printEnergySourceメソッドをオーバーライドしたサブクラスのインスタンスvehicle2を生成していますが,そのサブクラスのクラス名は特に定めていません. 1行目のnew Vehicle() {で生成したインスタンスはこの無名クラスのインスタンスですが,それをVehicleクラスの変数に代入しています. これは「アップキャスト」という処理で,スーパークラスの変数にサブクラスのインスタンスを代入することが出来ます. これは型変換(キャスト)ですが,アップキャストは暗黙に行うことが出来ます. 逆に,サブクラスの変数にスーパークラスのインスタンスを代入することを「ダウンキャスト」と言います. ダウンキャストは通常のキャストのように明示しなければなりません.