この記事の要点
• abstract class は「一部だけ実装を持つ」親クラス
• `abstract` メソッドはサブクラスが**必ず実装**しなければならない
• 抽象クラスは インスタンス化できない(サブクラス経由で使う)
このシリーズについて
「Java入門」は1本1テーマで進むシリーズです。本記事は第24回として、抽象クラス(abstract class)を扱います。
前提条件: Java入門 #21 - 継承(extends)、Java入門 #23 - インターフェース を読んでいること。クラス・継承・インターフェースの理解が必要です。
抽象クラスとは
抽象クラス(abstract class)は、一部のメソッドを未実装のまま残しておく ことができるクラスです。通常のクラスとインターフェースの中間的な存在で、次のような特徴があります。
| 項目 | 抽象クラス | インターフェース | 通常のクラス |
|---|---|---|---|
| 宣言 | abstract class Shape | interface Drawable | class Circle |
| 抽象メソッド | 持てる(abstract 付き) | デフォルトで抽象メソッド | 持てない(全部実装が必要) |
| 通常のメソッド | 持てる(実装あり) | default / static メソッドのみ | 持てる |
| フィールド | 持てる(状態を持てる) | public static final のみ | 持てる |
| コンストラクタ | 持てる(サブクラスから呼ばれる) | 持てない | 持てる |
| インスタンス化 | できない | できない | できる |
| 継承 / 実装 | 1つのクラスのみ extends | 複数 implements | 1つのクラスのみ extends |
抽象クラスの宣言
abstract キーワードを付けてクラスを宣言します。
// Shape.java(抽象クラス)
public abstract class Shape {
private String name;
// コンストラクタは持てる
public Shape(String name) {
this.name = name;
}
// 通常のメソッド(実装あり)
public String getName() {
return name;
}
// 抽象メソッド(実装なし)
public abstract double getArea();
public abstract double getPerimeter();
}
抽象クラスは インスタンス化できません。
Shape s = new Shape("未定義"); // コンパイルエラー!
// → 'Shape' is abstract; cannot be instantiated
抽象メソッドとは
抽象メソッド(abstract method)は、メソッドの宣言だけあって実装がない メソッドです。abstract キーワードを付けて宣言します。
public abstract double getArea(); // 実装なし(セミコロンで終わる)
抽象メソッドを持つクラスは、必ず abstract class として宣言 しなければなりません。
サブクラスでの実装義務
抽象クラスを継承したサブクラスは、すべての抽象メソッドを実装しなければなりません。実装しないとコンパイルエラーになります。
// Circle.java(具象クラス)
public class Circle extends Shape {
private double radius;
public Circle(String name, double radius) {
super(name); // 親のコンストラクタを呼ぶ
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
// Rectangle.java(具象クラス)
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String name, double width, double height) {
super(name);
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
}
// Main.java(Java 21)
public class Main {
public static void main(String[] args) {
Shape circle = new Circle("円", 5.0);
Shape rect = new Rectangle("長方形", 4.0, 6.0);
System.out.println(circle.getName() + " 面積: " + circle.getArea());
System.out.println(rect.getName() + " 面積: " + rect.getArea());
}
}
実行結果:
円 面積: 78.53981633974483
長方形 面積: 24.0
ポイント: 抽象クラスは「共通の処理は親が実装し、個別の処理は子に任せる」設計パターン(Template Method パターン)でよく使われます。
コンストラクタの扱い
抽象クラスは コンストラクタを持てます。サブクラスのコンストラクタから super() で呼ばれます。
// Animal.java(抽象クラス)
public abstract class Animal {
private String species;
// コンストラクタ
public Animal(String species) {
this.species = species;
System.out.println(species + " を作成");
}
public abstract void makeSound();
}
// Dog.java
public class Dog extends Animal {
public Dog() {
super("犬"); // 親のコンストラクタを呼ぶ
}
@Override
public void makeSound() {
System.out.println("ワン!");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); // → 犬 を作成
dog.makeSound(); // → ワン!
}
}
注意: 抽象クラス自体は `new Animal()` でインスタンス化できません。あくまでサブクラス経由で使います。
抽象クラス vs インターフェース
どちらを使うべきかは、設計の目的によって変わります。
| 使い分け | 抽象クラス | インターフェース |
|---|---|---|
| 「is-a 関係」を表す | ◎(Dog extends Animal) | △ |
| 「can-do 関係」を表す | △ | ◎(Dog implements Runnable) |
| 共通の状態(フィールド)を持つ | ◎(name, age など) | ✗(定数のみ) |
| 共通の実装を持つ | ◎(通常のメソッド) | △(default メソッド) |
| 複数を継承 / 実装したい | ✗(1つのみ) | ◎(複数可) |
| コンストラクタで初期化 | ◎ | ✗ |
使い分けの例
// 抽象クラス: 動物という「種類」を表す
public abstract class Animal {
private String name; // 共通の状態
public Animal(String name) {
this.name = name;
}
public abstract void makeSound();
}
// インターフェース: 「走れる」という能力を表す
public interface Runnable {
void run();
}
// Dog は動物(is-a)であり、走れる(can-do)
public class Dog extends Animal implements Runnable {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("ワン!");
}
@Override
public void run() {
System.out.println("走る");
}
}
学習のヒント: 「共通の状態やロジックを持つなら抽象クラス、役割や能力を表すならインターフェース」と覚えましょう。
実践例: テンプレートメソッドパターン
抽象クラスは、処理の流れを親が決めて、細部を子に任せる 設計によく使われます。
// Game.java(抽象クラス)
public abstract class Game {
// テンプレートメソッド(流れを定義)
public final void play() {
initialize();
startPlay();
endPlay();
}
// 共通処理
private void initialize() {
System.out.println("ゲームを初期化");
}
// 抽象メソッド(子クラスが実装)
protected abstract void startPlay();
protected abstract void endPlay();
}
// Chess.java
public class Chess extends Game {
@Override
protected void startPlay() {
System.out.println("チェスを開始");
}
@Override
protected void endPlay() {
System.out.println("チェスを終了");
}
}
// Shogi.java
public class Shogi extends Game {
@Override
protected void startPlay() {
System.out.println("将棋を開始");
}
@Override
protected void endPlay() {
System.out.println("将棋を終了");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Game chess = new Chess();
chess.play();
// → ゲームを初期化 → チェスを開始 → チェスを終了
System.out.println();
Game shogi = new Shogi();
shogi.play();
// → ゲームを初期化 → 将棋を開始 → 将棋を終了
}
}
play() の処理の流れは親クラスが決めて、startPlay() / endPlay() の中身は子クラスに任せています。
まとめ
| 概念 | 説明 |
|---|---|
| abstract class | 一部のメソッドを未実装のまま残せるクラス |
| abstract メソッド | 実装を持たないメソッド(サブクラスが必ず実装) |
| インスタンス化 | 抽象クラスは new できない(サブクラス経由で使う) |
| コンストラクタ | 抽象クラスも持てる(super() で呼ばれる) |
| 使い分け | 共通の状態やロジックがあるなら抽象クラス、役割や能力を表すならインターフェース |
次のステップ
Java入門 #25 - static と final の基本 で、クラスに紐づくメンバーと変更不可の仕組みを学びます。
参考リソース
- Abstract Methods and Classes(Oracle Java Tutorial) — 抽象クラスの公式ガイド
- Template Method Pattern — テンプレートメソッドパターンの解説
- Java SE 21 ドキュメント