はじめに
継承の次に必ず出てくるのが 抽象クラス と インターフェース。
どちらも「共通のルールを決める仕組み」だけど、役割が違います。
ここでは、たい焼き器を題材にして直感的に理解できる形で説明します。
それぞれのイメージ
抽象クラス:
→ 「たい焼き器の“設計図”」
→ 一部だけ“作り方が決まっていない”状態で残せる
インターフェース:
→ 「たい焼きを作るための“ルール表”」
→ 中身(処理)は一切書けない、ルールだけ
抽象クラス(abstract class)とは?
“共通の性質を持つけど、作り方が決まっていない部分があるクラス”
たい焼き器で例えると:
- たい焼き器には「加熱する」「掃除する」など共通の機能がある
でも「どう加熱するか」は種類によって違う
だから heat() だけ“未完成”のまま残す
抽象クラスのコード例(たい焼き器の例)
- 抽象クラスはclass宣言の前にabstractキーワードを入れる
abstract class TaiyakiMakerBase {
// 共通の処理(完成している)
void clean() {
System.out.println("たい焼き器を掃除する");
}
// 種類ごとに違うので“未完成”のまま
abstract void heat();
}
具体的なたい焼き器を子クラスで実装した例
//例1
class GasTaiyakiMaker extends TaiyakiMakerBase {
@Override
void heat() {
System.out.println("ガスで加熱する");
}
}
//例2
class ElectricTaiyakiMaker extends TaiyakiMakerBase {
@Override
void heat() {
System.out.println("電気で加熱する");
}
}
👉 抽象クラスは「共通部分+未完成部分」をセットで持てる。
継承時に抽象メソッドが残っているとそのクラスも抽象クラスになる。
インターフェース(interface)とは?
- “たい焼きを作るためのルールだけを書いたもの”
たい焼き器の世界で例えると:
- 「生地を流す」「具材を入れる」「焼く」など
“やるべきこと”だけ決めて、中身は書かない
インターフェースのコード例(たい焼き器のルール)
インターフェースは宣言時にclassではなくinterfaceで宣言する
interface TaiyakiProcess {
void pourBatter(); // 生地を流す
void addFilling(); // 具材を入れる
void bake(); // 焼く
}
ルールを実装するたい焼き器
実装時は継承と違い、implementsキーワードを使う
※メソッドは基本すべて抽象メソッドになる
class SimpleTaiyakiMaker implements TaiyakiProcess {
@Override
public void pourBatter() {
System.out.println("生地を流し込む");
}
@Override
public void addFilling() {
System.out.println("あんこを入れる");
}
@Override
public void bake() {
System.out.println("両面から加熱して焼く");
}
}
👉 インターフェースは“ルールだけ”。中身は必ず子クラスで書く。
※抽象メソッドが残っているとそのクラスは抽象クラスになる
図解:抽象クラス vs インターフェース
【抽象クラス】
TaiyakiMakerBase
├─ clean() ← 完成している
└─ heat() ← 抽象メソッド(未完成)
↓ extends
GasTaiyakiMaker
└─ heat() を実装
【インターフェース】
TaiyakiProcess
├─ pourBatter() ← ルールだけ
├─ addFilling() ← ルールだけ
└─ bake() ← ルールだけ
↓ implements
SimpleTaiyakiMaker
├─ pourBatter() を実装
├─ addFilling() を実装
└─ bake() を実装
違いをたい焼き器で比較
| 項目 | 抽象クラス | インターフェース |
|---|---|---|
| 役割 | 設計図(共通部分+未完成部分) | ルール表(やるべきことだけ) |
| 中身の有無 | 具体的な処理を書ける | 処理は書けない(Java8以降はdefault可) |
| 継承 | 1つだけ extends | 複数 implements できる |
| 使いどころ | 共通処理をまとめたい | ルールを強制したい |
どう使い分ける?
-
共通の処理をまとめたい → 抽象クラス
例:clean() は全たい焼き器で同じ -
やるべきことを強制したい → インターフェース
例:pourBatter(), addFilling(), bake() は必須
参考:インターフェースで具象メソッド(通常のメソッド)を持つ
Java8以降ではインターフェースでも具象メソッドが使えるようになった
① default メソッド(具象メソッド OK)
interface TaiyakiProcess {
default void clean() {
System.out.println("たい焼き器を掃除する");
}
}
- 「実装クラスが自由にオーバーライドできる」前提で
インターフェースなのに中身を書ける
② static メソッド(具象メソッド OK)
interface TaiyakiProcess {
static void info() {
System.out.println("たい焼き工程のインターフェースです");
}
}
- クラスメソッド的に使える
- 実装クラスからは呼べない(TaiyakiProcess.info() のみ)
③ private メソッド(Java 9以降)
interface TaiyakiProcess {
private void helper() {
System.out.println("内部処理");
}
}
- default メソッド同士で共通処理をまとめるための“裏方”