はじめに
Javaの学習を進めていくと、必ず出てくるのがこの2つの考え方:
- 継承(Inheritance)
- コンポジション(Composition)
「どちらを使えば良いのか分からない…」「違いがあいまいで困る…」という声をよく聞きます。
この記事では、そんなモヤモヤをスッキリ解消!
継承とコンポジションの違い・使い分け方を、小学生でもわかるように図とコードで解説します。
継承(Inheritance)とは?
意味:親の機能を子が引き継ぐ
継承とは、あるクラスが別のクラスの機能を「引き継ぐ(extend)」仕組みです。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Animal { public void eat() { System.out.println("食べる"); } } class Dog extends Animal { public void bark() { System.out.println("ワン!"); } } |
ポイント
- 「DogはAnimalである」= is-a の関係
- 子クラスは親クラスのメソッドをそのまま使える
- コードの再利用がしやすい
- 複数のサブクラスを同じ親クラスとして扱える(ポリモーフィズム)
コンポジション(Composition)とは?
意味:部品を組み合わせて使う
コンポジションは、あるクラスが別のクラスの「インスタンスを持つ」ことで機能を実現します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Engine { public void start() { System.out.println("エンジン始動"); } } class Car { private Engine engine = new Engine(); public void run() { engine.start(); System.out.println("走り出す"); } } |
ポイント
- 「CarはEngineを持っている」= has-a の関係
- 機能を自由に組み合わせられる
- 部品の変更・差し替えが簡単
- テストや保守がしやすい
【図解】継承 vs コンポジション
どっちを使えばいい?使い分け早見表
判断基準 | 継承(Inheritance) | コンポジション(Composition) |
---|---|---|
関係性 | is-a(〜である) | has-a(〜を持っている) |
拡張性 | 親の仕様に依存しやすい | 柔軟で差し替えやすい |
保守性 | 親の変更に弱い | 部品の変更に強い |
柔軟性 | 固定的な構造 | 組み合わせ自由 |
安全性 | 実装に強く依存 | インターフェースに依存 |
実例で比較!「車」と「エンジン」の設計
継承を使う例(あまりオススメしない)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Engine { public void start() { System.out.println("エンジン始動"); } } class Car extends Engine { public void drive() { start(); System.out.println("走る"); } } |
この例だと、CarはEngineであるという関係になってしまい、現実とは違いますよね?
コンポジションで書き直す(自然な設計)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Engine { public void start() { System.out.println("エンジン始動"); } } class Car { private Engine engine = new Engine(); public void drive() { engine.start(); System.out.println("走る"); } } |
この方が、「車はエンジンを持っている」という設計として自然です。
Effective Javaも「コンポジション推奨」
Javaの名著『Effective Java』でも、次のように書かれています:
継承は強力だが、誤って使うと壊れやすい設計になる。
多くの場合、コンポジションを使った方が柔軟で安全。
特に、他人が作ったクラスを継承する場合、内部実装の変更に弱くなるため、コンポジションを使う方が良いとされています。
よくある間違いと落とし穴
継承しすぎてクラスが複雑になる
- 継承を繰り返すと、変更の影響範囲が広がり、バグが出やすくなります。
継承してはいけないクラスを継承する
HashSet
を継承して機能追加したら、内部のaddAll()
が原因でバグが発生した…という実例も。
コンポジションで forwarding が面倒になる
1 2 3 4 |
public void startEngine() { engine.start(); } |
- 部品のメソッドを「転送」するコードが増えることもありますが、安全性とのトレードオフと考えましょう。
おすすめの判断基準
迷ったらコンポジションにしておく!
と覚えておくと、安全で柔軟なコードになります。
まとめ:継承とコンポジションは「道具の使い分け」
- 継承は「同じ型として扱いたいとき」「完全に同種のクラスであるとき」に使う
- コンポジションは「部品を組み合わせたいとき」「将来変更される可能性があるとき」に使う
- 設計の自由度・安全性を重視するならコンポジションの方が有利
次にするべきこと
自分でコードを書いて試してみよう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Printer { public void print() { System.out.println("印刷中"); } } class Office { private Printer printer = new Printer(); public void usePrinter() { printer.print(); } } |
このように、自分で「どっちを使うのが自然か?」を考えてみましょう。
自己学習したい方へ
「絶対にJavaプログラマーになりたい人へ。」を読んで、自己学習を進めるのがオススメです。
わからないとき・転職したいときは?
- 「コードレビューをしてほしい」
- 「学習が一人では難しい」
- 「Javaで転職したい!」
そんなときは、サイゼントアカデミーがあなたを全力サポートします。
コメント