【Java】「継承(Inheritance)」って何?共通点を見つけてコードをスッキリさせる魔法
はじめに
みなさんどうも、おげんです。
前回は、クラスの中身をしっかり守る「カプセル化」についてお話ししました。
「クラスの作り方」や「守り方」が分かってくると、次にこんな悩みが出てきませんか?
「似たようなクラスを作るたびに、同じコードを何度も書くの、正直めんどくさい……」
例えば、RPGを作ると想像してみてください。
「戦士」クラスにも「魔法使い」クラスにも、名前、HP、攻撃といった共通の機能が必要ですよね。
これらを毎回ゼロから作るのは、エンジニアとしては効率が悪い気がしてしまいます。
そこで登場するのが、オブジェクト指向の強力な武器「継承(Inheritance)」です。
今回は、この「継承」を使って、いかに効率よくスマートにコードを書くかというお話を解説していきます。
- 学習を始めて、「継承」という言葉に難しそうなイメージを持っている人
- 同じようなコードを何度も書くことに「楽できないかな?」と感じている人
- オブジェクト指向の基本を、難しい用語抜きでイメージから理解したい人
- 実務で役立つ「綺麗なコードの書き方」を知りたい人
なぜ「継承」が必要なのか?(具体例)
プログラムを書いていると、似たような機能を持つクラスを複数作りたい場面によく遭遇します。
例えば、RPGに登場する「戦士(Warrior)」と「魔法使い(Wizard)」のクラスを作るとしましょう。
どちらも「名前」があって「HP」があって「攻撃する」という行動をしますよね。
継承を使わずに、それぞれゼロから作るとこんな感じになります。
// 戦士クラス
public class Warrior {
String name;
int hp;
void attack() {
System.out.println(name + "の攻撃!");
}
}
// 魔法使いクラス
public class Wizard {
String name;
int hp;
void attack() {
System.out.println(name + "の攻撃!");
}
}見ての通り、name も hp も attack() メソッドも、全く同じ内容を2回書いています。
これにはいくつか問題があります。
- 修正が大変: もし「攻撃のメッセージにダメージ量を表示したい」となったら、両方のクラスを書き直さないといけません。
- ミスが起きやすい: 片方は直したけど、もう片方は忘れてた……なんていうバグの元になります。
- コードが長くなる: 似たようなクラスが10個、20個と増えたら、管理しきれません。
ここで登場するのが「継承」です。
共通する部分(名前、HP、攻撃)を一つの「親クラス(スーパークラス)」にまとめてしまい、それを「子クラス(サブクラス)」が引き継ぐことで、この二度手間をスパッと解決できるんです。
Javaでの書き方:extends を使おう
継承を使うには、Javaでは extends というキーワードを使います。 「拡張する」という言葉の通り、親の機能をベースにして、子クラスで新しい機能を付け足していくイメージです。
まずは、共通部分をまとめた「親クラス」を用意します。
① 親クラス(Character):共通部分をまとめる
// 親クラス(スーパークラス)
public class Character {
String name;
int hp;
void attack() {
System.out.println(name + "の攻撃!");
}
}② 子クラス(Warrior):親の機能を引き継ぐ
次に、戦士クラスを作ります。ここで extends Character と書くことで、Character の中身を引き継げます。
// 子クラス(サブクラス)
public class Warrior extends Character {
// nameやhp、attack()は書かなくても引き継がれています!
// 戦士だけの独自スキルを追加
void shieldBash() {
System.out.println(name + "のシールドバッシュ!");
}
}③ 実際に動かしてみる
メインの処理で動かしてみると、Warrior クラスには書いていないはずの name や attack() がしっかり使えていることがわかります。
public class Main {
public static void main(String[] args) {
// Warriorクラスをインスタンス化
Warrior warrior = new Warrior();
warrior.name = "おげん"; // 親(Character)の変数を使える
warrior.attack(); // 親(Character)のメソッドを呼び出せる
warrior.shieldBash(); // 子(Warrior)独自のメソッドも使える
}
}なんでも継承すればいいわけじゃない?
継承はとても便利ですが、実は「似ているから」という理由だけで使ってしまうと、後で大変なことになることがあります。
例えば、「スマートフォン」というクラスを作るとき、同じ「画面があって操作できるから」という理由で「テレビ」クラスを継承するのは…ちょっと違和感がありますよね。
「is-a関係」を意識しよう
継承を使うか迷ったときは、「子クラス is a 親クラス(子クラスは、親クラスの一種である)」という言葉が成り立つかどうかを確認してみてください。
- 戦士 is a キャラクター: 成り立つ!(継承OK)
- 魔法使い is a キャラクター: 成り立つ!(継承OK)
- スマートフォン is a テレビ: 成り立たない……(継承NG!)
「is-a関係」が成り立たないのに無理やり継承してしまうと、後から「親クラスを修正したら、関係ないはずの子クラスまで変な動きになっちゃった!」というトラブルの原因になります。
「共通点があるから継承!」ではなく、「同じ種類のグループだから継承!」と考えるのが、スマートなエンジニアへの第一歩です。
まとめ:継承を使いこなして「楽に」コードを書こう
今回は、オブジェクト指向の重要な柱のひとつ「継承」についてお話ししました。
ポイントを振り返ると、
- 継承(extends): 親クラスの機能を子クラスに引き継ぐこと。
- メリット: 同じコードを何度も書かなくて済むし、修正も一箇所で済む。
- オーバーライド: 親のメソッドを、子クラスで自分流に書き換えること。
- 注意点: 「is-a関係(〜は、〜の一種である)」が成り立つ時に使うのが正解!
最初は「親」だの「子」だの、少しややこしく感じるかもしれません。
でも、実際にコードを書いて「あ、これ書かなくていいんだ!楽だな!」と感じた瞬間から、継承の面白さが分かってくるはずです。
これからの実務でも、きっとこの「効率よく、綺麗に書く」という考え方が役に立つと信じて、これからも学びを進めていこうと思います!
今回も最後まで読んでいただき、ありがとうございました!
また次の一歩を、着実に進めていきましょう。
