継承とは
継承を簡単に説明すると、以前作ったクラスと似たクラスを作ることを可能にする機能のことです。
継承することで新しい機能を追加すれば、簡単に元のクラスから発展したクラスを作成することが可能になります。
「クラス」がよくわからない人は、以下の記事を参考にしてみるといいでしょう。
【関連記事】
▶︎【Java】初心者必見!クラスについてわかりやすく解説。
継承を使ったクラスの例・書き方(extends)
継承では、extendsを使って表現します。具体的な記述方法は以下の通りです。
class クラス名 extends 元となるクラス名 { 親クラスとの「差分」メンバ }
簡単に、サンプルコードで動きを確認してみましょう。以下のサンプルは「Monster.java」「Slime .java」「Main..java」の3つのファイルに分かれています。
// Monster.java public class Monster { String name = "モンスター"; // 攻撃 public void attack(String name) { System.out.println(name + "の攻撃"); System.out.println(name + "は20のダメージを与えた"); } // 逃亡 public void escape(String name) { System.out.println(name + "は逃げ出した"); } }
// Slime.java(Monsterクラスを継承) public class Slime extends Monster { String name = "スライム"; // ジャンプ public void jump() { System.out.println(this.name + "は飛び跳ねた"); } }
Slimeクラスは、Monsterクラスの機能に加えて、jump()という独自の機能を追加したクラスです。
Mainクラスで、Slimeクラスがインスタンス化される時に、SlimeクラスはMonsterクラスに含まれている、attack()、escape()をもっていると判断します。
// Main.java public class Main { public static void main(String[] args) { Monster mt = new Monster(); mt.attack(mt.name); mt.escape(mt.name); // mt.jump(); // 実行するとエラーになります。 System.out.println("-------------------------------"); Slime sm = new Slime(); sm.attack(sm.name); sm.jump(); sm.escape(sm.name); } }
上記のプログラムを実行すると、以下の実行結果が得られます。
実行結果↓
ドラゴンの攻撃 ドラゴンは20のダメージを与えた ドラゴンは逃げ出した ------------------------------- スライムの攻撃 スライムは20のダメージを与えた スライムは飛び跳ねた スライムは逃げ出した
Mainクラスに書かれているように、Monsterのインスタンスでjump()を実行しようとしても、エラーが表示されるハズです。
これは、jump()がSlimeクラスでのみ実行可能な機能である証拠になります。
継承を使うメリット
継承の使い方はわかりましたが、継承を使うメリットってあるのでしょうか?元となるコードをコピー&ペーストして、新しい機能を追加した方が簡単なのでは?
使い慣れていない人にとっては、継承は難しく感じるかもしれません。ここでは、継承を使うメリットを2つご紹介しましょう。
Javaで継承を使うメリットは、以下の2つです。
- メソッド(機能)の追加・修正が簡単になる
- メソッド(機能)の把握や管理が簡単になる
簡単にそれぞれの理由をご紹介します。
メソッド(機能)の追加・修正が簡単になる
例えば、継承を使わずに元クラス(Monsterクラス)のコードをコピー&ペーストして、新しいクラス(Slimeクラス)を作成した場合を考えましょう。
Monsterクラスに新しいメソッドを加えたりattack()の内容を修正すると、その変更をSlimeクラスにも行う必要が出てきます。
その点、継承を使ってSlimeクラスを実装すると、Monsterクラスで変更した部分も自動的にSlimeクラスに反映されます。
クラスが1つ2つの話であれば、元クラスのコピー&ペーストでも構いませんが、より複雑で大きなプログラムを作る場合は非常に面倒くさいです。
メソッド(機能)の把握や管理が簡単になる
継承を使わないでクラスを作成すると、似たようなコード、ソースコードの大半が重複したファイルがたくさん作成されることになるでしょう。
これでは、プログラム全体の見通しが悪くなり、メンテナンスが難しくなります。
メソッドの把握や管理が簡単になるのも、追加・修正同様に複雑なプログラムになればなるほど、継承は効果的な方法であることがわかります。
継承を使うことで、
- メソッドの追加や修正が1回で済む
- メソッドの管理や、プログラムの見通しが良くなる
といったメリットが生まれます。
Javaは、複数の親からの継承不可(多重継承)
継承は、2つのクラスの関係だけではなく、元のクラスをベースにして複数の子クラスを定義することができます。
さらに、孫クラスや曾孫クラスのように、どんどん深いところまでクラスを定義することも可能です。
ただし、Javaは複数のクラスを親として1つの子クラスを定義することはできないと決まっています。
オーバーライドとは
オーバーライドとは、親クラスを継承して子クラスを宣言する時に、親クラスのメンバを子クラスで上書きすることです。
親クラスで定義されているメソッドの中身を、少し変えて使いたい場合に使用されます。
ここでは、先ほどのサンプルプログラムを用いて、オーバーライドをしてみましょう。
似た言葉に「オーバーロード」がありますが、全く異なるものなので混同しないように注意です。
オーバーライドの例・書き方
先ほどのSlimeクラスで、Monsterクラスのescape()の文言を少し変えてみましょう。
// Slime.java(Monsterクラスを継承) public class Slime extends Monster { String name = "スライム"; // ジャンプ public void jump() { System.out.println(this.name + "は飛び跳ねた"); } // 逃亡 public void escape(String name) { System.out.println(name + "はあなたに怯えているようだ"); System.out.println(name + "は一目散に逃げ出した"); } }
Mainではescape()だけ実行してみます。
// Main.java ファイル public class Main { public static void main(String[] args) { Monster mt = new Monster(); mt.escape(mt.name); Slime sm = new Slime(); sm.escape(sm.name); } }
上記のプログラムを実行すると、以下の実行結果が得られます。
実行結果↓
モンスターは逃げ出した スライムはあなたに怯えているようだ スライムは一目散に逃げ出した
MonsterクラスとSlimeクラスで、それぞれ定義されているescape()が実行されているのがわかります。
継承やオーバーライドを禁止にする(final)
クラスを作成する時にfinal装飾子を使うと、継承やオーバーライドを禁止することができます。final装飾子は、以下のようにpublicの後ろに記述しましょう。
// 継承禁止 public final class クラス名 { // オーバーライド禁止 public final void メソッド名 { // 処理内容 } }
先ほどのMonsterクラスや、escape()にfinal装飾子をつけると、プログラム実行時にエラーが出るのが確認できます。
Stringクラスは継承禁止
Javaでは、Stringクラスの継承は禁止です。
JavaのAPIリファレンスを確認すると、Stringクラスはfinal装飾子がついており、継承禁止になっているのがわかります。
https://docs.oracle.com/javase/jp/8/docs/api/
インスタンスとは?
インスタンスは、「オブジェクト」とも言われ、クラスの中にある機能(メソッド)を実行するための”実体”と言えます。
少しわかりにくい存在ですが、インスタンスが存在することによって、初めてクラスの中にある機能を実行できるのです。
親インスタンス部にアクセスする方法(super)
親インスタンス部のメソッドを使用したい時は、superを使ってアクセス・参照します。具体的な例を以下で見ていきましょう。
Slimeクラスを以下のように変更しました。スライムが攻撃した場合はMonsterクラスのescape()文言を使用する、といった内容です。
// Slime.java ファイル (Monsterクラスを継承) public class Slime extends Monster { String name = "スライム"; // ジャンプ public void jump() { System.out.println(this.name + "は飛び跳ねた"); } // 攻撃 private boolean attack = false; public void attack(String name) { System.out.println(name + "の攻撃"); System.out.println(name + "は5のダメージを与えた"); this.attack = true; } // 逃亡 public void escape(String name) { if(this.attack) { super.escape(name); } else { System.out.println(name + "はあなたに怯えているようだ"); System.out.println(name + "は一目散に逃げ出した"); } } }
super.escape(name);
でMonsterクラスのescape()を利用します。
// Main.java ファイル public class Main { public static void main(String[] args) { Slime sm = new Slime(); // 下記の sm.attack(sm.name); の有無でescape()の文言が変わります。 sm.attack(sm.name); sm.escape(sm.name); } }
上記のプログラムを実行すると、以下の実行結果が得られます。
実行結果↓
スライムの攻撃 スライムは5のダメージを与えた スライムは逃げ出した
Monsterクラスのescape()が実行されているのがわかります。
コンストラクタとは?
コンストラクタとは、インスタンスを生成する時に実行される機能のことをいいます。少しわかりにくいかもしれませんが、「初期値」を設定するようなイメージでOKです。
サンプルプログラムの中の
Monster mt = new Monster(); Slime sm = new Slime();
で、インスタンスを生成し、コンストラクタの呼び出されます。
Javaのコンストラクタについては、以下の記事で詳しく説明をしているので参考にしてください。
【関連記事】
▶︎Javaコンストラクタとは?基礎からしっかり理解しよう!
Javaの継承まとめ
Javaで継承がしっかり使えると、似たようなクラスを応用して新しいクラスで効率的に機能の追加ができます。
また、プログラムが複雑になっても修正や追加が簡単になり、メンテナンスや管理面でもメリットが豊富です。
ぜひ、この記事を参考に継承を上手に活用してください。
この定義は多重継承といい、Javaでは許可されていない定義方法です。