【はじめてのJava】オーバーライドの注意点【クラスの継承編】
はじめてのJava
このシリーズでは、初めてJavaやプログラミングを勉強する方向けに、Javaによるプログラミングの基礎を説明していきます。
目標レベルは、Javaの資格試験の一つである「Oracle Certified Java Programmer, Silver」(通称Java Silver)に合格できる程度の知識の習得です。
はじめてJavaやプログラムに触れる方にもできるだけわかりやすい解説を心がけていきます。
クラスの継承編
クラスの継承編では、Javaを扱う上で重要な「クラスの継承」について扱っていきます。
前回はメソッドのオーバーライドを実践しました。
今回はオーバーライドの注意点を扱います。
目次
オーバーライドの注意点
前回の記事で、メソッドのオーバーライドを実践しました。その時のCarクラスとPoliceCarクラスは以下の通りです。
Car.java
class Car { double speed; String color; void accell(){ this.speed += 10; //speedを10増加させる } }
PoliceCar.java
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //ここから追記 //オーバーライド void accell(){ if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } //ここまで }
前回の記事では「このソースコードにはちょっとした問題点がある」と書きましたが、一見すると、問題が無いように見えます。いったいどこに注意が必要なのでしょうか。
オーバーライドの注意点
では、実際にオーバーライドの注意点を見てみましょう。
改めて、先ほどのソースコードを見てみます。
Car.java
class Car { double speed; String color; void accell(){ this.speed += 10; //speedを10増加させる } }
PoliceCar.java
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //ここから追記 //オーバーライド void accell(){ if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } //ここまで }
上記のソースコードはオーバーライドが成功しています。
しかし、もしPoliceCarクラスを書く際に、以下のようにaccellの綴りを間違えてしまったら、どうなるでしょうか。
accellのつづりを間違えたPoliceCar.java
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //ここから追記 //オーバーライドのつもり void accel(){ //lが足りない・・・。 if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } //ここまで }
この状態で前回と同じPoliceDriveクラスを実行してみましょう。
PoliceDriveクラスは以下の通りです。(前回から変更していません)
PoliceDrive.java
public class PoliceDrive{ public static void main(String[] args){ PoliceCar panda = new PoliceCar(); panda.speed = 0.0; System.out.println(panda.speed); panda.isChasing = false; //追跡していない状態 panda.accell(); //accellを踏むとspeedが10増加 System.out.println(panda.speed); //↑の確認用に表示 System.out.println("############"); //このままだと分かりづらいので表示時の境界線を出力 panda.speed = 0.0; //一度pandaのspeedをリセット System.out.println(panda.speed); panda.isChasing = true; //追跡状態 panda.accell(); //accellを踏むとspeedが20増加 System.out.println(panda.speed); //↑の確認用に表示 } }
この状態で実行すると以下のようになってしまいます。
つづり間違いのために、Carクラスで定義したaccellメソッドが実行されてしまうため、本来であればspeedが20増加してほしいときも10しか増加しません。
つづり間違いさえしなければ防げますが、つづり間違いをしない前提で書くのは非現実的です。
さらに、つづり間違えたaccel()メソッドは、メソッドの作り方としては問題が無く、コンパイルエラーが出ないことも、この問題点の厄介なところです。
ちょっとやそっと見た程度では、どこに問題があるのか見当がつきません。
これでは困ってしまいます。では、どうすればよいのでしょうか。
@Overrideアノテーション
今回問題だったのは「オーバーライドしたつもりでオーバーライドになっていない上、コンパイルエラーになっていない」点です。
あくまでもJVMは「文法的にエラーがあったときのみコンパイルエラー」とします。今回のようにエラーになってほしくても文法的に問題が無ければそのままコンパイルされてしまいます。
もしコンパイラに何らかの方法で「この場合は警告してほしい」と注釈をつけることができれば、解決できそうです。
では、どうすればよいのでしょうか。
アノテーション
Javaにはアノテーションという機能があります。
アノテーションはコンパイラに何らかの注釈を与えることができる機能です。
今回の場合はアノテーションを用いて「これはオーバライドのつもりで定義しているけれど、オーバーライドとしておかしかったらエラーにしてほしい」という注釈を与えることで、コンパイラが「オーバーライドの形式を守っているかどうか」の判断もしてくれるようになります。
@Override
「@Override」というアノテーションを使うと「オーバーライドのつもりで定義しています」という注釈をつけることができます。
実際にアノテーションを使って「オーバーライドのつもりで定義しています」という注釈をつけてみましょう。
やり方は簡単で、子クラス内でオーバーライドしているメソッドの直前に「@Override」と記述するだけです。
@Override //ここにオーバーライドで上書きするメソッドを定義する
では、実際に先ほどのPoliceCarクラスで試してみましょう。
オーバーライドの形式が正しい場合
オーバーライドの形式が正しい場合は以下の通りです。
PoliceCar.java(オーバーライドの形式が正しい場合)
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //↓アノテーションを付けてオーバーライドであることを注釈 @Override void accell(){ if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } }
この場合、コンパイル・実行ともに、今までと変わりません。
オーバーライドの形式が間違いっている場合
では、オーバーライドの形式が間違っているとどうなるのでしょうか。
accellをつづり間違えてた場合を例に見てみます。
PoliceCar.java(オーバーライドの形式が間違いっている場合)
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //オーバーライドのつもりだが、accellの綴りを間違えている↓ @Override void accel(){ if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } }
この場合、コンパイルすると以下のように表示されコンパイルエラーとなります。
このように、@Overrideアノテーションを使うと、「気づかずにオーバーライドに失敗して動作がおかしくなる」ことを防げます。
サンプルプログラム
今回作成したサンプルプログラムは以下の通りです。なお、3つのファイルは同じディレクトリに保存して動作を確認してください。
Car.java、PoliceDrive.javaには手を加えていませんが、動作確認するためには3クラスとも必要になります。よくわからない場合は必ず同じディレクトリに3つのファイルを保存しておいてください。
Car.java
class Car { double speed; String color; void accell(){ this.speed += 10; //speedを10増加させる } }
class PoliceCar extends Car{ boolean isChasing; //追跡中ならtrue、それ以外はfalse void questioning(){ //とりあえず、Freeze!!(動くな!!)と表示 System.out.println("Freeze!!"); } //↓アノテーションを付けてオーバーライドであることを注釈 @Override void accell(){ if(this.isChasing){ //isChasingがtrueのとき this.speed += 20; //speedを20増やす }else{ //isChasingがfalseのとき this.speed += 10; //speedを10増加させる } } }
PoliceDrive.java
public class PoliceDrive{ public static void main(String[] args){ PoliceCar panda = new PoliceCar(); panda.speed = 0.0; System.out.println(panda.speed); panda.isChasing = false; //追跡していない状態 panda.accell(); //accellを踏むとspeedが10増加 System.out.println(panda.speed); //↑の確認用に表示 System.out.println("############"); //このままだと分かりづらいので表示時の境界線を出力 panda.speed = 0.0; //一度pandaのspeedをリセット System.out.println(panda.speed); panda.isChasing = true; //追跡状態 panda.accell(); //accellを踏むとspeedが20増加 System.out.println(panda.speed); //↑の確認用に表示 } }
まとめ
オーバーライドの形式が間違っていても文法上の誤りが無い場合コンパイルエラーにならない
@Overrideアノテーションを付けることで「オーバーライドとして正しい形式かどうか」をコンパイル時にチェックすることができるようになる
次回
次回もクラスの継承について扱います。
はじめてのJavaシリーズの目次はこちら
クラスの継承編はこちら