アットウィキロゴ
Javaプログラミング入門
掲示板 掲示板 ページ検索 ページ検索 メニュー メニュー

Javaプログラミング入門

17. 拡張による継承

最終更新:

javatutorial

- view
管理者のみ編集可

これまでの不便な点と「継承」のメリット

前回の「カプセル化」では、クラスのデータを安全に守る方法を学びました。
しかし、新しくクラスを作るときに「以前作ったあのクラスとほとんど同じだけど、少しだけ機能を追加したクラスを作りたい」と思うことはありませんか?

例えば、RPGゲームで「キャラクター(Character)」クラスを作ったとします。キャラクターは「名前(name)」を持ち、「攻撃(attack)」ができます。
次に、魔法が使える「魔法使い(Wizard)」クラスを作ろうとしました。魔法使いもキャラクターの一種なので、名前を持ち、攻撃もできます。さらに「魔法(magic)」という新しい機能も持っています。

これまでの知識では、魔法使いクラスを作るために、キャラクタークラスと同じ「名前」や「攻撃」のプログラムを最初からもう一度書き直す(コピー&ペーストする)必要がありました。
しかし、これではプログラムが長くなり、もし「攻撃」の仕組みを変更したいときに、両方のクラスを一つずつ直さなければならず大変です。

これを解決するのが、継承という仕組みです。
継承を使うと、すでにあるクラスの機能をそのまま引き継いで、新しい機能だけを「拡張(追加)」することができます。

拡張による継承の基本構文

クラスを継承して新しいクラスを作るには、extends(エクステンズ)というキーワードを使います。
extendsには「拡張する」という意味があります。
class 新しいクラス名 extends 元になるクラス名 {
    // 新しく追加したい機能(フィールドやメソッド)だけを書く
} 
ここで、プログラミングの重要な専門用語を2つ覚えましょう。
継承で使われるクラスの呼び方
元になるクラス 親クラス(またはスーパークラス)
新しく作られたクラス 子クラス(またはサブクラス)
子クラスは、親クラスが持っているフィールド(変数)やメソッドを自動的にすべて使えるようになります。
そのため、子クラスの中には「追加したい新しい機能」だけを書けばよいのです。

継承を使ったプログラム例

実際に継承を使ってプログラムを書いてみましょう。
まず、親クラスとなる「キャラクター(Character)」クラスを作成します。前回のカプセル化のルールに従って、フィールドはprivateにし、メソッドを用意します。

サンプル:Character.java
  1. public class Character {
  2. private String name;
  3.  
  4. // 名前をセットするメソッド
  5. public void setName(String name) {
  6. this.name = name;
  7. }
  8.  
  9. // 名前を取得するメソッド
  10. public String getName() {
  11. return this.name;
  12. }
  13.  
  14. // 攻撃するメソッド
  15. public void attack() {
  16. System.out.println(this.getName() + "の通常の攻撃!");
  17. }
  18. }
次に、このCharacterクラスを継承して、新しい「勇者(Hero)」クラスを作ります。

サンプル:Hero.java
  1. // Characterクラスを拡張(extends)してHeroクラスを作る
  2. public class Hero extends Character {
  3. // 勇者だけの新しい機能(回復魔法)を追加
  4. public void heal() {
  5. System.out.println(this.getName() + "は回復魔法をとなえた!体力が回復した!");
  6. }
  7. }

最後に、メインプログラムで動かしてみましょう。

サンプル:Main.java
  1. public class Main {
  2. public static void main(String[] args) {
  3. // 子クラスである勇者(Hero)を生み出す
  4. Hero hero = new Hero();
  5.  
  6. // 親クラス(Character)から引き継いだメソッドが使える!
  7. hero.setName("伝説の勇者");
  8. hero.attack();
  9.  
  10. // 子クラス(Hero)で新しく追加したメソッドも当然使える!
  11. hero.heal();
  12. }
  13. }
実行結果
伝説の勇者の通常の攻撃!
伝説の勇者は回復魔法をとなえた!体力が回復した! 

どうでしょうか?
Heroクラスの中には「setName」や「attack」を書いていないのに、親クラスであるCharacterクラスを継承しているため、当たり前のように使うことができました。
これが継承の最大のメリットです。

💡注意!
子クラスは親クラスの機能をすべて引き継ぎますが、親クラスのフィールドがprivateで隠されている場合、子クラスからでも直接中身を見ることはできません。
その場合は、現段階では上のプログラムのように親クラスのpublicなメソッド(getNameなど)を経由してアクセスする必要があります。

親の機能を上書きする「オーバーライド」

継承を使うと、親から引き継いだメソッドの動きを、子クラス専用に書き換える(上書きする)こともできます。
これをオーバーライドと呼びます。
例えば、勇者(Hero)の攻撃は、普通のキャラクターの攻撃よりも派手なメッセージにしたいとします。
その場合、子クラスの中に親と全く同じ名前のメソッドを書くことで上書きされます。

サンプル:Hero.java
  1. public class Hero extends Character {
  2. // 親クラスのattack()メソッドを上書き(オーバーライド)
  3. public void attack() {
  4. System.out.println(this.getName() + "の会心の一撃!!大ダメージを与えた!");
  5. }
  6.  
  7. public void heal() {
  8. System.out.println(this.getName() + "は回復魔法をとなえた!体力が回復した!");
  9. }
  10. }
このように書くと、Heroクラスの attack() を実行したときは、親の地味な攻撃ではなく、子の派手な攻撃が優先して実行されるようになります。

本当に上書きされているか、メインのプログラムを実行して確認してみましょう。
普通のキャラクター(Character)と勇者(Hero)を両方作り、それぞれに攻撃(attack)の命令を出して動きを比べてみます。

サンプル:Main.java
  1. public class Main {
  2. public static void main(String[] args) {
  3. // 親クラス(普通のキャラクター)を生み出す
  4. Character chara = new Character();
  5. chara.setName("村人");
  6.  
  7. // 子クラス(勇者)を生み出す
  8. Hero hero = new Hero();
  9. hero.setName("伝説の勇者");
  10.  
  11. // それぞれの攻撃を実行して違いを確認する
  12. chara.attack();
  13. hero.attack();
  14. }
  15. }
実行結果
村人の通常の攻撃!
伝説の勇者の会心の一撃!!大ダメージを与えた! 
実行結果を見ると、まったく同じ「attack()」という命令を出しているのに、普通のキャラクターは親クラスに書かれた地味な攻撃を行い、勇者は子クラスで上書きした派手な攻撃を行っていることがわかりますね。
これがオーバーライドの力です。

オーバーライドの目印

オーバーライドをするときは、メソッドの上に @Override (アットマーク・オーバーライド)という目印を書くのが一般的です。
これは「親のメソッドを上書きしていますよ」というコンピュータへの合図になります。書かなくても動きますが、書いておくことで間違い(つづりミスなど)を防ぐことができます。
@Override
public void attack() {
    System.out.println("会心の一撃!!");
} 

曖昧なクラスから実体を作らせない「抽象クラス」

先ほどのMainのプログラムでは、new Character() として実体を作り、「村人」として扱っていました。
しかし、実際のゲーム開発などでは「キャラクター」という曖昧な枠組みそのものを、直接生み出したくない(new したくない)場面がよくあります。

例えば、レストランで店員さんに「『食べ物』を一つください!」と注文しても、ハンバーガーを出せばいいのか、サラダを出せばいいのか困ってしまいますよね。
注文するときは必ず「ハンバーガー」といった具体的なメニューを指定するはずです。

プログラミングの世界もこれと同じです。
「キャラクター」というクラスは、「名前」や「攻撃」といった共通のルールをまとめただけの「カテゴリ(分類)」にすぎません。
もし画面にただの「キャラクター」を直接登場させようとしても、「どんなグラフィック(見た目)なのか?」「歩くスピードはどれくらいか?」といった具体的なデータが足りないため、ゲームがエラーで止まってしまう原因になります。
画面に出すなら、必ず「勇者」や「魔法使い」、あるいは「村人」といった、すべてのデータが揃っている具体的な子クラスとして生み出さなければなりません。

しかし、通常のクラスのままだと、開発チームの他のプログラマーがうっかり new Character() と書いて中途半端な実体を作ってしまっても、コンピュータは文法上の間違いではないと判断し、エラーを出さずにそのまま動かそうとしてしまいます。
これが後々、予期せぬバグ(不具合)に繋がるのです。

そんな「間違った使われ方(実体化)」をシステム的に防いでくれるのが、抽象クラスです。

クラスの前に abstract というキーワードをつけると、そのクラスは「継承の親として使う専用の設計図」になり、直接 new して実体(インスタンス)を作ることができなくなります

サンプル:Character.java(抽象クラス版)
  1. // abstractをつけると、new Character() が禁止される
  2. public abstract class Character {
  3. private String name;
  4.  
  5. public void setName(String name) {
  6. this.name = name;
  7. }
  8.  
  9. public String getName() {
  10. return this.name;
  11. }
  12.  
  13. // 中身({ }の中)を書かず、子クラスに上書きを強制する「抽象メソッド」
  14. public abstract void attack();
  15. }

子クラスにルールの作成を強制する「抽象メソッド」

上記のプログラムの attack() メソッドにも abstract がついていることに注目してください。
このように、メソッドの前に abstract をつけ、処理の中身( { } )を書かずに ; で終わらせたものを 抽象メソッド と呼びます。

抽象メソッドを持った親クラスを継承した子クラス(勇者など)は、必ずそのメソッドをオーバーライド(上書き)して中身を作らなければならないという厳しいルールが発生します。
もし子クラスで attack() を作り忘れると、コンパイルエラー(文法間違い)として教えてくれるため、多人数でのプログラム開発での「作り忘れ」を防ぐ強力な味方になります。

コラム

すべてのクラスの「ご先祖様」とは?

今回、extendsを使って親クラスを指定する方法を学びました。では、extendsを何も書かなかったクラス(今まで作ってきたクラスや、今回のCharacterクラス)には親がいないのでしょうか?
実は、Javaの世界では「すべてのクラスは、一番の基本となる java.lang.Object というクラスを自動的に継承する」という深いルール(JVMに基づく言語仕様)が存在します。
私たちがextendsを書かなくても、プログラムを動かす裏側では勝手に extends Object と補ってくれているのです。つまり、Javaに存在するすべてのクラスは、Objectクラスの子孫ということになります。
過去に変数などを画面に表示する際などに、見慣れない「toString()」や「equals()」といったメソッドが使えることがあるのは、この一番の先祖であるObjectクラスから継承して引き継いでいるからなのです。

最近更新されたスレッド
人気記事ランキング
ウィキ募集バナー