「継承とポリモーフィズム」って、名前のクセが強いなあ!
Javaの学習を始めたばかりの頃、テキストに出てきた言葉たち。
その中でも、ひときわインパクトが強かったのがこの2つ。
「継承」
「ポリモーフィズム」
読めるけど意味が分からない。
どこかの魔法学校で習う呪文かな?と思ったのは私だけではないはずです。
本を読んでみても、「コードの再利用性が〜」「多態性によって〜」と説明され、(いや、だから“ポリモーフィズム”って何なの…)
と、ページを閉じたのも一度や二度ではありません。
でも、ちょっとずつコードを書いて、試して、つまずいて、 そのたびに「なるほど、そういうことか!」と点が線に変わる瞬間がありました。
この記事では、そんな私の“理解できなかった頃のモヤモヤ”を思い出しながら、Javaにおける継承とポリモーフィズムの基礎を、実例を交えて解説していきます。
「とりあえず extends って書いとけばいいんでしょ?」という頃の私のようなあなたに、 「設計ってちょっとおもしろいかも」と思える入り口になれば嬉しいです。
継承?ポリモーフィズム?聞き慣れない言葉がやってきた
突然あらわれた「継承」と「ポリモーフィズム」
Javaを学び始めて少し経った頃、クラスやメソッドにも慣れてきて、 「少しは成長したかも…!」と思った矢先にやってきたのがこの2語。
継承と、ポリモーフィズム。
いや、聞き慣れなさすぎませんか?
カタカナの主張が強すぎて、ついWikipediaを開いたものの、「多態性により、型の抽象化を実現する」 「親クラスの特性を子クラスが…」
…読んだはずなのに脳内で霧が晴れないあの感覚。
私も何度味わったことか。
「オブジェクト指向ってなんかすごそう」から始まる道
そもそも「オブジェクト指向」自体が、ちょっと遠い世界の話に思えがちです。
でも実際には、とてもシンプルな目的を持った考え方なんです。
それはズバリ、「複雑なプログラムを、分かりやすく安全に作るための発明」。
- 繰り返し同じコードを書くのが大変 → 再利用できる仕組みが欲しい
- 似たような機能でも、ちょっとだけ挙動を変えたい → 柔軟に拡張できる方法が欲しい
そんな “現場のリアルな悩み” に向き合って生まれたのが、継承(Inheritance)とポリモーフィズム(Polymorphism) という発想なんです。
実はあなたも「継承の恩恵」を受けている?
Javaを書いていると、知らないうちに使っていることがあります。
public class MyList extends ArrayList<String> {
// ArrayListの便利機能がそのまま使える!
}
この extends というキーワードこそ、継承の証。
ArrayList という「親クラス」の機能を、「子クラス」が丸ごと受け継いで使っています。
「え、じゃあ最初から全部親に任せとけばいいじゃん」と思うかもしれませんが、それだけではダメ。
そこに「必要な部分だけカスタマイズする」という工夫が登場し、ポリモーフィズムの役割が見えてきます。
この章のまとめ:まずは“ことばの壁”を超えてみよう
- 「継承」=親の力を受け継ぐ仕組み
- 「ポリモーフィズム」=共通のルールで、違う振る舞いを持たせる仕組み
- どちらも、コードの柔軟性・再利用性を高めるための知恵
【継承入門】親から子へ、コードを受け継ぐしくみ
継承って、どういう仕組み?
継承(Inheritance)とは、あるクラスが別のクラスの機能を引き継ぐことを指します。
Javaでは、extends というキーワードで実現できます。
public class Animal {
void speak() {
System.out.println("なにかしゃべるよ!");
}
}
public class Dog extends Animal {
// ここには speak() がなくても…
}
Dog pochi = new Dog();
pochi.speak(); // → 「なにかしゃべるよ!」と出力される!
そう、DogクラスはAnimalクラスのspeak()を“受け継いで”使えるんです。
これが、継承の基本的な使い方。
親(スーパークラス)と子(サブクラス)の関係
Javaでは、
- 継承される元のクラスを「スーパークラス(親)」
- 継承する側のクラスを「サブクラス(子)」 と呼びます。
Dog extends Animal なら、Animalが親で、Dogが子。
まるで「親の性格をちょっと引き継いで、でも自分らしく成長する子」みたいなイメージです。
継承のメリットは「コードの再利用」
継承の大きな目的は、重複するコードを避けて、共通の処理をまとめることです。
例:複数の動物がいる場合、それぞれのクラスに eat() や sleep() を毎回書くのは面倒ですよね?
そこで、こうします
public class Animal {
void eat() {
System.out.println("もぐもぐ…");
}
void sleep() {
System.out.println("すやすや…");
}
}
public class Cat extends Animal {
void meow() {
System.out.println("にゃーん");
}
}
Cat tama = new Cat();
tama.eat(); // 親から継承
tama.meow(); // 子だけのオリジナル
共通する処理は親に集約して、個性は子に任せる。 これが継承を使った“うまい設計”です。
継承の注意点:「なんでも継承」はNG!
「便利だから何でも継承しよう!」と考えると、 やがて “謎の親に頼りすぎたワカメ構造” になります。
- 意味のない親子関係(例:Printer extends Sandwich)
- 親の修正が子に無関係でも影響する
- 子の責務が不明瞭になる
こんなときは、「それ、本当に“is-a”の関係かな?」と立ち止まってみましょう。
たとえば、
- 「猫は動物である」→ Cat is an Animal(継承OK)
- 「自転車は車である」→?微妙…
- 「プリンタはサンドイッチである」→ダメ!
この章のまとめ:継承の“うまい使い方”を身につけよう
- extends を使えば、クラスを「親 → 子」へ拡張できる
- 継承は 共通処理をまとめる&再利用するのが基本
- 「is-a関係」が成り立つときに継承を使うのが◎
- 親に詰め込みすぎたり、意味のない継承は避けるべし
【オーバーライド入門】親のメソッド、書き換えていいんです
オーバーライドとは「自分流にアレンジできる仕組み」
継承によって、子クラスは親クラスの機能をまるっと使えるようになりました。
でも…「このまま使うにはちょっと都合が悪いんだけど…」という場面、実は結構あるんです。
そんなときに登場するのが、オーバーライド(Override)。
読んで字のごとく、親クラスのメソッドを“上書きして、自分流に変える”ための機能です。
例:動物の鳴き声をカスタマイズしたい!
public class Animal {
void speak() {
System.out.println("何かが鳴いています。");
}
}
public class Dog extends Animal {
@Override
void speak() {
System.out.println("ワンワン!");
}
}
public class Cat extends Animal {
@Override
void speak() {
System.out.println("ニャー!");
}
}
このように、親クラスのspeak()を子クラス側で同じシグネチャ(戻り値・引数・名前)で書き直すことで、「上書き」ができます。
@Override アノテーションってなんのため?
Javaでは、オーバーライドするメソッドの上に
@Override
と書くのが慣習です。
これは単なる飾りではなく、「これは親クラスのメソッドをちゃんと上書きしていますよ」と教えてくれる目印。
もし、名前が違ったり、引数を間違えてたりしていると…ちゃんとエラーで教えてくれます。
// ミスしてる例(名前が間違ってる)
@Override
void speek() {
System.out.println("間違った綴り…");
}
// → エラーになる!(ありがたい)
つまり、@Overrideは“正しく上書きできてるかチェックしてくれるセーフティネット”みたいな存在です。
オーバーライドのすごいところ=「同じ命令で違う動作をさせられる」
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.speak(); // → ワンワン!
a2.speak(); // → ニャー!
見てください。どっちもAnimal型で、speak()を呼んでるだけ。
でも、中身がDogかCatかによって振る舞いが変わってる!
これがまさに、次章で詳しく説明する「ポリモーフィズム(多態性)」の準備なんです!
オーバーライドでよくあるミス
- 戻り値や引数が親と違っている(正しく上書きされない)
- アクセス修飾子の制限:親より厳しくしてはいけない!
例:親がpublicなら、子はprotectedやdefaultにできない - staticメソッドはオーバーライドではなく「隠蔽」になる(このあたりは応用編で)
この章のまとめ:自分だけの動きを定義できるのがオーバーライド!
- オーバーライドとは、親クラスのメソッドを“同じ名前で”上書きすること
- @Override を書くことで、安全かつミスに気づける
- 同じ型でも中身によって違う動作をさせる準備になる(=ポリモーフィズムへの道)
【ポリモーフィズム入門】同じメソッド名、でも動きが変わる?
「ポリ…なに?言いにくさ世界選手権」の代表選手
「ポリモーフィズム(Polymorphism)」
――初めて出会ったとき、「舌噛みそう」と思ったのは私だけじゃないはずです。
そして説明を読んでもたいていはこうなります。
「多態性のこと。あるクラスが共通のインターフェースを通じて異なる動作をすること。」
…え?つまりどういうこと?
この章では、そんな言葉の壁を取り払いながら、「ポリモーフィズム」の正体をゆっくり丁寧に見ていきます!
ポリモーフィズム=“同じメッセージに、違う反応を返す力”
この現象、実はすでにあなたも経験しています。
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.speak(); // → ワンワン!
a2.speak(); // → ニャー!
ここで注目してほしいのが、「a1 も a2 も型は Animal」だということ。
つまり “Animal型という共通のルール”に従ってコードを書いているのに、中身に応じて違う動きになる。
これこそがポリモーフィズムなんです!
「ポリモーフィズム」とは、同じメソッド名で異なる動作を実現するテクニック。
どうしてこんなことができるの?
答えは、前の章で紹介した オーバーライド にあります。
親クラスで定義されたメソッドを、子クラスで自分用に上書き(オーバーライド)することで、 “親として扱っているのに、子の個性が出せる” という現象が起こります。
この仕組みによって、以下のようなメリットが得られます。
- コードの柔軟性が高まる:追加されるクラスに対して、親クラス側のコードを変えずに対応できる
- 統一的に処理を書ける:同じ型で扱えるので、for文やメソッド引数にも柔軟に使える
例:動物たちを一括処理したいときに便利!
Animal[] zoo = { new Dog(), new Cat(), new Bird() };
for (Animal animal : zoo) {
animal.speak();
}
ここではすべて Animal 型の配列にしていますが、 それぞれの中身に応じて Dog → ワンワン!、Cat → ニャー!、Bird → ピヨピヨ! と振る舞いが変化します。
「動物たち、発声せよ!」と命令するだけで、それぞれが自分らしい反応を返す。
これがポリモーフィズムの“魔法”です!
よくある混乱:「オーバーロードと何が違うの?」
オーバーロードとポリモーフィズムは、よく混同されがちです。
簡単に整理すると…
比較項目 | オーバーロード | ポリモーフィズム |
---|---|---|
発生場所 | 同じクラス内で複数のメソッド定義 | 親子関係を利用して、子クラスで振る舞いを変える |
メソッド名 | 同じ | 同じ |
引数 | 異なる(数や型) | 同じ |
実行時 or コンパイル時 | コンパイル時に決定 | 実行時に決定(動的バインディング) |
この章のまとめ:多様性は強さ!
- ポリモーフィズムは「共通の型で、違う振る舞いを引き出せる」しくみ
- オーバーライドによって、それぞれのクラスに“個性”を与えることができる
- 統一的なコードで拡張性・柔軟性の高い設計が可能になる
- オーバーロードとの違いにも注意!(決まるタイミングが違うよ)
継承とポリモーフィズムはどう使い分けるの?
「継承でいいのか?それとも別の手段があるのか?」
ここまで、継承とポリモーフィズムがどれほど便利かを見てきました。
でも、いざ自分で設計しようとするとこうなりがちです。
「このクラス、親から継承させるべき?」
「でも、無理やり継承すると逆にややこしいような…?」
そう、“継承の便利さ”と“使いどころ”は別問題。
この章では、「継承すべき場面」と「やめた方がいい場面」を見極めるための考え方をご紹介します!
継承は「is-a」関係のときに使う!
Javaの継承は、「〇〇は△△の一種である(is-a)」という関係が成り立つときに自然に使えます。
継承が適切な例
class Animal {
void eat() {}
}
class Dog extends Animal {
void bark() {}
}
- DogはAnimalである(Dog is an Animal)→ OK!
継承が不自然な例
class Printer {
void print() {}
}
class Report extends Printer {
void generate() {}
}
- ReportはPrinterではない → 無理やりすぎる…
こういうときは、無理せず別の方法(委譲や共通インターフェース)を検討しましょう。
「コードを共有したいだけなら継承しない」が正解のときもある
「同じ処理を共有したい」=すぐ継承ではありません!
たとえば、DogとCatが同じwalk()メソッドを必要としていても、 必ずしもそれらをAnimalにまとめる必要はありません。
代わりに、「歩くという行動を持つクラスに処理を“委譲”する」という考え方があります。
class Walker {
void walk() {
System.out.println("てくてく歩く");
}
}
class Dog {
Walker walker = new Walker();
void walk() {
walker.walk();
}
}
このように、クラスの責務を分けて「委譲(Composition)」することで柔軟性を保つ設計ができます。
継承 vs 委譲(Composition)のざっくり比較
観点 | 継承(Inheritance) | 委譲(Composition) |
---|---|---|
関係性 | is-a(〜は〜である) | has-a(〜は〜を持っている) |
柔軟性 | 低い(親の変更に影響) | 高い(差し替えやすい) |
再利用のしやすさ | 手軽に機能をまとめられる | 関心ごとを分離できてテストしやすい |
変更への強さ | 弱い(親子が密結合) | 強い(緩くつながる) |
設計で迷ったときのチェックリスト
以下のような問いかけをすると、継承すべきか判断しやすくなります。
- ⭕️ 「〇〇は△△の一種である」と言えるか?
- ⭕️ 親クラスのすべての機能を本当に必要としているか?
- ⭕️ 子クラスを増やしても、設計が破綻しないか?
- ❌ コードを共有したいだけで、継承していないか?
- ❌ 親クラスに変更があると、子クラスが壊れそうではないか?
まとめ:道具を正しく選べば、設計はもっと心地よくなる!
- 継承は便利だけど「どこでも使えばOK」ではない
- 本当に「is-a」の関係か?と立ち止まるクセをつけよう
- 継承だけでなく「委譲」や「インターフェース」も柔軟な設計には有効!
- 設計に正解はないけれど、「読みやすさ・変更のしやすさ」を意識するのが大切
まとめ:今日からクラス設計の見え方が変わる
「クラス設計って、こう考えるんだ」が見えてきた!
最初はまるで呪文のように感じた「継承」や「ポリモーフィズム」も、 コードと一緒に見てみると、「なんか使えそうかも」という感覚がつかめてきたのではないでしょうか。
- 「親から受け継ぐことでコードをすっきり書ける」
- 「同じメソッド名で振る舞いを変えることができる」
- 「でも、便利だからといって何でも継承してはいけない」
そう、クラス設計には“選ぶチカラ”が必要なんです。
そしてその選択肢のひとつが、「継承」や「ポリモーフィズム」というわけですね。
このシリーズで学んだことをざっくりおさらい
学んだこと
- 継承・ポリモーフィズムという概念の“壁”は、ちゃんと乗り越えられる!
- extends を使って、親から機能を受け継げる
- オーバーライドで、親のメソッドを自分流にアレンジできる
- ポリモーフィズムは「共通の型で違う動きをさせる」強力な仕組み
- 継承の使いすぎには注意!必要なら委譲やインターフェースを選ぶことも設計力
この流れを通して、「書くだけでなく、設計する」視点が少しずつ身についたはずです!
設計に“正解”はない。だからこそ「なぜそうしたか」が大切
クラス設計は一言で言えば“選び方の芸術”です。
- このクラス、継承すべきか?
- 共通処理は親に書く?それとも外部に任せる?
- 将来的な変更にも耐えられる構造になっているか?
初心者のうちは難しく感じるかもしれませんが、「なぜその設計をしたのか」を言葉にできることが何より大切。
それができると、コードの“読みやすさ”も“保守性”もぐっと上がっていきますよ。

最初はとっつきにくい「継承」や「ポリモーフィズム」も、実は「コードと仲良くなるための知恵」だった—— そんなふうに感じていただけていたら、この記事を書いた意味があったなと思います。
私自身、学び始めた頃は「ポリモー…なんだっけ?」と立ち止まったり、 extends とか @Override にビクビクしながらコードを書いたりしていました。 でも少しずつ、仕組みを知って、使って、つまずいて、また書いて—— そうやって育てていく実感こそが、プログラミングの一番おもしろいところだと思っています。
コメント