【はじめてのJava】オーバーライドの注意点2【クラスの継承編】
はじめてのJava
このシリーズでは、初めてJavaやプログラミングを勉強する方向けに、Javaによるプログラミングの基礎を説明していきます。
目標レベルは、Javaの資格試験の一つである「Oracle Certified Java Programmer, Silver」(通称Java Silver)に合格できる程度の知識の習得です。
はじめてJavaやプログラムに触れる方にもできるだけわかりやすい解説を心がけていきます。
クラスの継承編
クラスの継承編では、Javaを扱う上で重要な「クラスの継承」について扱っていきます。
前回は正しくないオーバーライドを防止する方法について紹介しました。
今回はオーバーライド時のアクセス修飾子について扱います。
目次
オーバーライドの注意点
前回の記事で、Carクラスのaccellメソッドを例に、オーバーライドの間違いを防ぐ「@Overrideアノテーション」を紹介しました。
今回はCarクラスを使ってオーバーライドの際のアクセス修飾子に関する注意点を考えていきます。
今回は以下のようにbrakeメソッドを搭載したCarクラスを使用します。
Car.java
class Car { double speed; String color; //アクセル void accell(){ this.speed += 10; } //ブレーキ void brake(){ if(this.speed >= 10){ this.speed -= 10; }else{ this.speed = 0; } } }
アクセス修飾子
アクセス修飾子とは以前紹介した通り別のクラスやパッケージから利用可能かどうかを制御する修飾子です。
例えば、publicなら他のクラスからでも自由に呼び出せますし、privateなら他のクラスからは一切呼び出しができません。
(詳しくはこちらの記事を参照)
実は、オーバーライドをする際にはアクセス修飾子に制限がかかります。
継承元のメソッドと比べて利用可能な範囲が同じか、広がるようなアクセス修飾子にする必要があります 。
どういうことでしょうか。
アクセス可能な範囲が変わらない場合
オーバーライドの前後でアクセス修飾子が同じ場合は、アクセス可能な範囲は変わりません。
具体例で考えてみましょう。先ほどのCarクラスを継承したPoliceCarクラスを作成し、brakeメソッドをオーバーライドます。
この時、アクセス修飾子を変更せずにオーバーライドする場合は以下の図のようなイメージになります。
ソースコードとしては以下の通りです。
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } @Override //アクセス修飾子は変えずにオーバーライド void brake(){ if(this.isChasing){ //追跡中なら普通にブレーキ this.speed -= 10; }else{ //追跡中以外は止まる this.speed = 0; } } }
この場合はPoliceCarでも問題なくbrakeを利用できます。
ではもし、オーバーライド時にアクセス範囲が狭まるようにアクセス修飾子が変更されてしまった場合はどうなってしまうのでしょうか。
アクセス可能な範囲を狭めた場合
次に、オーバーライド時にアクセス範囲が狭まるようにアクセス修飾子を変更した場合を考えます。
先ほどと同じように、Carクラスを継承したBadPoliceCarクラスを考えます。
イメージ図を考えると以下のようになります。
オーバーライド前のCarクラスのbrakeメソッドはパッケージデフォルトと呼ばれる、アクセス修飾子が無い状態です。
それに対してオーバーライド後のBadPoliceCarクラスのbrakeメソッドにはprivateというアクセス修飾子がついています。
アクセス修飾子がprivateの場合は、そのクラス(この場合はPoliceCar)の内部からしか呼び出すことができません。
その結果、Carクラスのbrakeメソッドは呼び出せるのにBadPoliceCarクラスのbrakeメソッドは呼び出せない、という事態が発生します。
つまり、外部から見ると Carクラスにはあったはずのbrakeが消滅した ように見えてしまうことになります。
あったはずのブレーキがなくなった・・・となると、恐ろしくて使うことはできません。そういった事態を避けるために、アクセス可能な範囲(可視性)を狭めた場合、 コンパイルエラー になるようになっています。
サンプルプログラム
今回作成したサンプルプログラムは以下の通りです。なお、すべて同じディレクトリに保存して動作を確認してください。
Car.java
class Car { double speed; String color; //アクセル void accell(){ this.speed += 10; } //ブレーキ void brake(){ if(this.speed >= 10){ this.speed -= 10; }else{ this.speed = 0; } } }
PoliceCar.java
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } @Override //アクセス修飾子は変えずにオーバーライド void brake(){ if(this.isChasing){ //追跡中なら普通にブレーキ this.speed -= 10; }else{ //追跡中以外は止まる this.speed = 0; } } }
BadPoliceCar.java
※コンパイルエラーになります。比較用に掲載。
class BadPoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } @Override private void brake(){ //アクセス修飾子の可視性が下がっているためコンパイルエラー if(this.isChasing){ this.speed -= 10; }else{ this.speed = 0; } } }
PoliceDrive.java
public class PoliceDrive{ public static void main(String[] args){ PoliceCar panda = new PoliceCar(); //BadPoliceCar badCar = new BadPoliceCar(); //そもそもBadPoliceCarがコンパイルエラーなのでここでコンパイルエラー panda.brake(); //OK PoliceCarは止まれる。 //badCar.brake(); //NG BadPoliceCarは止まれない・・・。 } }
まとめ
オーバーライドの際にはアクセス修飾子の可視性が下がるとコンパイルエラー
次回
次回もクラスの継承について扱います。
はじめてのJavaシリーズの目次はこちら
クラスの継承編はこちら