|
|
クラスの継承 |
H.Kamifuji . |
ここではクラスの継承について解説していきます。 当ページでは、Linux CentOS7 の Gnome で動作テストしています。 現在(2021/11)では、JDK-17.0.1 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2023/04)では、JDK-20.0.1 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2024/10)では、JDK-23 にアップされています。一部、上位互換について、見直しを行っていきます。 |
|
まずクラスの継承とは何かについて見ていきます。 例として2つのクラスを作成することを考えて見ます。2種類の車に関するクラスを作成してみます。 class carA{ public void accele(){ .... } public void brake(){ .... } public void hybrid(){ .... } } class carB{ public void accele(){ .... } public void brake(){ .... } public void openRoof(){ .... } }carAとcarBはそれぞれ異なる特徴があるとします。ただ車としての基本的な機能は同じです。2つのクラスをそれぞれ別々に設計すれば同じ機能があるにも関わらずどちらも同じ記述をしなければいけない部分が出てきてしまいます。 そこで車としての基本的な設計部分は共通して、異なる部分だけ追加して設計すれば便利です。このような場合に、基本となるクラスをスーパークラスと言います。例えば下記のようなスーパークラスを作成してみます。 class car{ public void accele(){ .... } public void brake(){ .... } }carAとcarBは、この基本クラスをベースに必要な部分を追加してクラスを設計します。 class carA extends car{ public void hybrid(){ .... } } class carB extends car{ public void openRoof(){ .... } }これで随分とすっきりしました。この時、carAとcarBはどちらもcarクラスを継承していると言います。またcarクラスがスーパークラスと呼ばれるのに対して、carAとcarBはcarクラスのサブクラスと呼ばれます。 継承の記述方法クラスの継承を行う場合には下記のように記述を行います。class subclass_name extends superclass_name{ }クラス定義をを行う際に「extends」の後に継承したいスーパークラス名を記述します。 注意する点として、クラスの継承は一度に1つしか出来ないことです。継承したいクラスが複数あったとしても、Javaでは一度に1つのスーパークラスしか継承することは出来ません。 では次項のページから詳しくクラスの継承について見ていきます。 |
ここではスーパークラスで定義されたメンバ変数やメンバメソッドへのアクセスについて確認していきます。 まず下記のようなクラスを作成します。Carクラスがスーパークラス、SoarerクラスはCarクラスを継承しています。 class Car{ int speed = 0; public void accele(){ speed += 5; } public void brake(){ speed -= 5; } } class Soarer extends Car{ boolean roofOpenFlag = false; public void openRoof(){ roofOpenFlag = true; } public void closeRoof(){ roofOpenFlag = true; } }次にSoarerクラスのオブジェクトを作成します。 class ctest11{ public static void main(String args[]){ Soarer soarer = new Soarer(); } }Soarerクラスのオブジェクトは、Soarerクラス名で定義されたメソッドを実行したり、変数を参照することが出来ます。 class ctest11{ public static void main(String args[]){ Soarer soarer = new Soarer(); soarer.openRoof(); if (soarer.roofOpenFlag){ System.out.println("屋根が開いています"); }else{ System.out.println("屋根が閉じています"); } } }またスーパークラスであるCarクラスのメソッドを実行したり変数を参照する事も可能です。 class ctest11{ public static void main(String args[]){ Soarer soarer = new Soarer(); soarer.openRoof(); if (soarer.roofOpenFlag){ System.out.println("屋根が開いています"); }else{ System.out.println("屋根が閉じています"); } soarer.accele(); System.out.println("現在の速度は" + soarer.speed + "キロです"); } }このようにあるクラスを継承したクラスは、自分自身で定義されたものの他に、継承したスーパークラスのメソッドを実行したりメンバ変数を参照することが可能です。 では実際に試してみましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest11{ public static void main(String args[]){ Soarer soarer = new Soarer(); soarer.openRoof(); if (soarer.roofOpenFlag){ System.out.println("屋根が開いています"); }else{ System.out.println("屋根が閉じています"); } soarer.accele(); System.out.println("現在の速度は" + soarer.speed + "キロです"); } } class Car{ int speed = 0; public void accele(){ speed += 5; } public void brake(){ speed -= 5; } } class Soarer extends Car{ boolean roofOpenFlag = false; public void openRoof(){ roofOpenFlag = true; } public void closeRoof(){ roofOpenFlag = true; } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ java ctest11 屋根が開いています 現在の速度は5キロです [xxxxxxxx@dddddddddd Extends]$ |
メンバ変数やメンバメソッドに対するアクセス制限に関しては『publicとprivate』でも記述した通り「public」を付けた場合(又はアクセスに関する修飾子を省略した場合)は、クラス外からでも直接アクセスが可能です。また「private」を付けた場合はクラス内からしかアクセスが出来ません。 そこでスーパークラスのメンバ変数やメソッドに「private」を付けた場合、そのスーパークラスを継承したサブクラスからは呼び出しができるかどうか試してみます。下記のような簡単なサンプルを作成し、スーパークラス内で定義されたpublicメンバとprivateメンバに、それぞれサブクラス内からアクセスしてみます。 サンプルプログラム下記のサンプルを実行してみよう。class ctest12{ public static void main(String args[]){ B sample = new B(); } } class A{ public int var = 0; private int varErr = 0; public int get(){ return var; } private int getError(){ return var; } } class B extends A{ public void getVar(){ System.out.println("値:" + var); System.out.println("値:" + varErr); System.out.println("値:" + get()); System.out.println("値:" + getError()); } }実際にコンパイルしてみると下記のようにコンパイルの時点でエラーとなります。 [xxxxxxxx@dddddddddd Extends]$ javac ctest12.java ctest12.java:23: エラー: varErrはAでprivateアクセスされます System.out.println("値:" + varErr); ^ ctest12.java:25: エラー: シンボルを見つけられません System.out.println("値:" + getError()); ^ シンボル: メソッド getError() 場所: クラス B エラー2個 [xxxxxxxx@dddddddddd Extends]$このようにサブクラス内からもprivateなメンバ変数やメンバメソッドにはアクセスできません。当然のことながらサブクラスのオブジェクトからもアクセスは出来ません。 protectedアクセス修飾子にはもう1つ「protected」という修飾子があります。この修飾子はサブクラスからスーパークラスのメンバへアクセスが出来るという意味合いがある修飾子で「public」と同じような働きをします。例えば下記のような例で考えて見ます。 サンプルプログラム下記のサンプルを実行してみよう。class ctest13{ public static void main(String args[]){ B sample = new B(); System.out.println("サブクラスからのアクセス"); sample.getVar(); System.out.println("他クラスからのアクセス"); System.out.println("値:" + sample.get()); } } class A{ protected int var = 0; protected int get(){ return var; } } class B extends A{ public void getVar(){ System.out.println("値:" + var); System.out.println("値:" + get()); } }スーパークラス内で定義された「protected」なメンバ変数やメンバメソッドは、サブクラス内からもアクセスが出来ますし、他のクラス内からもアクセスが可能です。その為、ほぼ「public」相当の働きとなります。ただ意味合い的には継承されたサブクラス内からアクセスされるようなメンバに対して付けることになっていますので、「public」と「protected」は明確に分けて使います。 またパッケージという概念を使う場合には「public」と「protected」は異なる挙動を示します。「public」は異なるパッケージからでもアクセス可能ですが、「protected」は異なるパッケージからは「private」と同等となります。このあたりの詳しい内容はパッケージの説明の際に見ていきます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest13.java [xxxxxxxx@dddddddddd Extends]$ java ctest13 サブクラスからのアクセス 値:0 値:0 他クラスからのアクセス 値:0 [xxxxxxxx@dddddddddd Extends]$ |
クラスを作成した際にコンストラクタを作成しているとまずコンストラクタが呼ばれますが、他のクラスを継承している場合にスーパークラスのコンストラクタの扱いがどうなっているのかを確認します。 下記のような簡単なサンプルを作成します。クラスBはクラスAを継承し、クラスCはクラスBを継承しています。それぞれにコンストラクタを記述してからクラスCのオブジェクトを作成してみましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest14{ public static void main(String args[]){ C sample = new C(); } } class A{ A(){ System.out.println("クラスAのコンストラクタ"); } } class B extends A{ B(){ System.out.println("クラスBのコンストラクタ"); } } class C extends B{ C(){ System.out.println("クラスCのコンストラクタ"); } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest14.java [xxxxxxxx@kddddddddd Extends]$ java ctest14 クラスAのコンストラクタ クラスBのコンストラクタ クラスCのコンストラクタ [xxxxxxxx@kddddddddd Extends]$見ていただくとお分かりのように大元のクラスのコンストラクタから順に呼ばれています。 クラスの継承を行った場合は、特に記述しなくてもコンストラクタの先頭で「super()」が呼ばれています。その為、先ほどのサンプルは下記のように記述したことと同じです。 class ctest14{ public static void main(String args[]){ C sample = new C(); } } class A{ A(){ super(); System.out.println("クラスAのコンストラクタ"); } } class B extends A{ B(){ super(); System.out.println("クラスBのコンストラクタ"); } } class C extends B{ C(){ super(); System.out.println("クラスCのコンストラクタ"); } }※クラスAは明示的に他のクラスを継承はしていませんが、クラスは基本的に「java.lang.Object」のサブクラスとなっています。その為、全てのクラスはjava.lang.Objectのコンストラクタが必ず呼ばれています。 引数のあるスーパークラスのコンストラクタ特に記述しなければ「super()」が呼ばれてスーパークラスの引数無しのコンストラクタが呼び出されていましたが、スーパークラスで引数があるようなコンストラクタが定義されている場合にそのコンストラクタの方を呼び出すことも可能です。引数のあるスーパークラスのコンストラクタを呼び出すには「super(引数)」の形で呼び出します。 簡単なサンプルで試して見ましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest15{ public static void main(String args[]){ B sample = new B(); } } class A{ A(){ System.out.println("クラスAのコンストラクタ"); } A(int num){ System.out.println("クラスAの引数有りコンストラクタ"); } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest15.java [xxxxxxxx@dddddddddd Extends]$ java ctest15 クラスAのコンストラクタ クラスBのコンストラクタ [xxxxxxxx@dddddddddd Extends]$superはコンストラクタの1行目に必ず記述しなければなりませんので注意して下さい。(よって当然のことながら2つのスーパークラスのコンストラクタを呼び出すことはできません)。 |
クラスを継承した時に元になっているスーパークラスで定義されているメソッドを継承したサブクラスにて同じメソッド名(と同じ引数)で書き換えることが出来ます。つまり上書きするということです。これをメソッドのオーバーライドと言います。 具体的な例で考えてみます。スーパークラスとしてクラスAを用意し、クラスAを継承したクラスB1、クラスB2があったとします。スーパークラスであるクラスAには「disp」というメソッドが定義されています。ここでクラスB1で「disp」というメソッドをオーバーライドしてみます。 class A{ public void disp(){ System.out.println("電化製品です"); } } class B1 extends A{ public void disp(){ System.out.println("エアコンです"); } } class B2 extends A{ }クラスB2のオブジェクトを作成し、「disp」メソッドを呼んだ場合、クラスB2自身には「disp」メソッドが定義されていませんのでスーパーク ラスであるクラスAで定義されている「disp」メソッドが呼ばれ"電化製品です"と表示されます。これに対してクラスB1のオブジェクトを作成し「disp」メソッドを呼んだ場合には、クラスB1自体に「disp」メソッドが定義されているのでクラスB1の「disp」メソッドが呼ばれ"エアコンです"と表示されます。 なぜこのようなことをするのかと言えば、スーパークラスで「disp」メソッドを定義することで、スーパークラスを継承した全てのサブクラスでは共通したメソッド名である「disp」メソッドが使えることになります。ただサブクラスによってはそのメソッドの定義を独自の内容に書き換えたい場合があります。このような場合に同じメソッド名と引数を使って再定義することで、メソッド名は共通ですが中身は定義しているクラスの合わせた内容に変更する事が出来るわけです。 サブクラス全部に共通のメソッド名を持たせながら、中身は自由に書き換えることが出来るというわけです。 では実際に試してみましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest16{ public static void main(String args[]){ B1 b1 = new B1(); b1.disp(); B2 b2 = new B2(); b2.disp(); } } class A{ public void disp(){ System.out.println("電化製品です"); } } class B1 extends A{ public void disp(){ System.out.println("エアコンです"); } } class B2 extends A{ }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest16.java [xxxxxxxx@dddddddddd Extends]$ java ctest16 エアコンです 電化製品です [xxxxxxxx@dddddddddd Extends]$ スーパークラスのメソッドを呼び出すオーバーライドしている場合でも、明示的にスーパークラスの方で定義されたメソッドの方を呼び出すことが可能です。その場合には「super」を使って下記のように呼び出します。class A{ public void disp(){ System.out.println("電化製品です"); } } class B extends A{ public void disp(){ System.out.println("エアコンです"); super.disp(); } }「super」を付けて呼び出した場合には、自身で定義したメソッドではなくスーパークラスで定義されたメソッドを呼び出します。 簡単なサンプルで試して見ましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest17{ public static void main(String args[]){ C c = new C(); c.disp(); } } class A{ public void disp(){ System.out.println("電化製品です"); } } class B extends A{ public void disp(){ System.out.println("エアコンです"); super.disp(); } } class C extends B{ public void disp(){ System.out.println("三菱製です"); super.disp(); } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest17.java [xxxxxxxx@dddddddddd Extends]$ java ctest17 三菱製です エアコンです 電化製品です [xxxxxxxx@dddddddddd Extends]$今回はクラスAをクラスBが継承し、クラスBをクラスCが継承しています。クラスCの「disp」メソッドを呼び出した場合、まずクラスCで定義されている「disp」メソッドが呼ばれます。次に「super.disp()」によってクラスCのスーパークラスであるクラスBで定義された「disp」メソッドが呼ばれています。つまり「super」を付けてメソッドを呼び出した場合は、一番大元のクラスのメソッドではなく、一つ上のクラスのメソッド(つまり継承している1つ上のスーパークラスのメソッド)が呼び出されます。 今回の場合は、クラスCのスーパークラスであるクラスBの「disp」メソッド内で、クラスBのスーパークラスであるクラスAの「disp」メソッドを呼び出すように記述しましたので、結果的に「クラスCのdispメソッド」、「クラスBのdispメソッド」、「クラスAのdispメソッド」が順に呼ばれることになります。 |
継承したクラスのオブジェクトを扱う場合、通常は下記のように利用します。 class test{ public static void main(String args[]){ subClass obj = new subClass(); } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClass extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } public void dispVersion(){ System.out.println("バージョン1.0です"); } }上記のようにオブジェクトを作成するクラスの変数を使うのですが、クラスを継承した場合、スーパークラスの変数に対してサブクラスのオブジェクトを割り当てることが出来ます。 スーパークラス 変数名 = new サブクラス();具体的には下記のようになります。 class test{ public static void main(String args[]){ superClass obj = new subClass(); obj.dispName(); } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClass extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } public void dispVersion(){ System.out.println("バージョン1.0です"); } }スーパークラスの変数にサブクラスのオブジェクトを格納した場合、このオブジェクトに対して実行できるメソッドはスーパークラスで定義されているものだけになります。もしサブクラスでオーバーライドされているのであれば、サブクラスの方のメソッドが呼び出されます。 例えば先ほどのサンプルで言えば、サブクラスで新たに追加された「dispVersion」メソッドはスーパークラスの変数に格納されたオブジェクトからは呼び出すことが出来ませんが、スーパークラスで定義されサブクラスでオーバーライドされている「dispName」メソッドは呼び出すことが出来ます。 では実際に試してみましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest18{ public static void main(String args[]){ superClass obj = new subClass(); obj.dispName(); } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClass extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } public void dispVersion(){ System.out.println("バージョン1.0です"); } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest18.java [xxxxxxxx@dddddddddd Extends]$ java ctest18 製品名はXXXです [xxxxxxxx@dddddddddd Extends]$上記のように、オーバーライドされたメソッドを呼び出す場合、サブクラスの方で定義されたメソッドが呼び出されます。 スーパークラス変数を使う理由では何故このようなことをするかですが、同じスーパークラスから複数の種類のサブクラスを作成した場合、スーパークラスの変数を使えばどのサブクラスのオブジェクトでも同じように扱うことが出来るためです。例えばサブクラスの変数名をそれぞれ使った場合、あるサブクラスの変数に別のサブクラスのオブジェクトを格納することは出来ませんので、それぞれ別々に扱わなければなりません。 class test{ public static void main(String args[]){ subClassA obj1 = new subClassA(); subClassB obj2 = new subClassB(); subClassC obj3 = new subClassC(); obj1.dispName(); obj2.dispName(); obj3.dispName(); } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClassA extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } } class subClassB extends superClass{ public void dispName(){ System.out.println("製品名はYYYです"); } } class subClassC extends superClass{ public void dispName(){ System.out.println("製品名はZZZです"); } }これに対してスーパークラスの変数を使う場合には、スーパークラスの変数にはどのサブクラスのオブジェクトでも格納できますので、統一した形で扱うことが出来るようになります。 class test{ public static void main(String args[]){ superClass obj[] = new superClass[3]; obj[0] = new subClassA(); obj[1] = new subClassB(); obj[2] = new subClassC(); for (int i = 0 ; i < 3 ; i++){ obj[i].dispName(); } } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClassA extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } } class subClassB extends superClass{ public void dispName(){ System.out.println("製品名はYYYです"); } } class subClassC extends superClass{ public void dispName(){ System.out.println("製品名はZZZです"); } }このように同じ型の変数に色々なサブクラスのオブジェクトを格納することで、サブクラスの違いを気にする事なくプログラムを記述できます。またスーパークラスの変数に格納したオブジェクトから利用できるのはスーパークラスで定義されたメソッド(及びサブクラスでオーバーライドされたメソッド)だけですので、どのサブクラスでも同じメソッド名を使うことができるわけです。このように扱うことができるのが継承をつかったプログラミングのメリットの1つです。 簡単なサンプルで試して見ましょう。 サンプルプログラム下記のサンプルを実行してみよう。class ctest19{ public static void main(String args[]){ superClass obj[] = new superClass[3]; obj[0] = new subClassA(); obj[1] = new subClassB(); obj[2] = new subClassC(); for (int i = 0 ; i < 3 ; i++){ obj[i].dispName(); } } } class superClass{ public void dispName(){ System.out.println("未定義です"); } } class subClassA extends superClass{ public void dispName(){ System.out.println("製品名はXXXです"); } } class subClassB extends superClass{ public void dispName(){ System.out.println("製品名はYYYです"); } } class subClassC extends superClass{ public void dispName(){ System.out.println("製品名はZZZです"); } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Extends]$ javac ctest19.java [xxxxxxxx@dddddddddd Extends]$ java ctest19 製品名はXXXです 製品名はYYYです 製品名はZZZです [xxxxxxxx@dddddddddd Extends]$ |
|