Javaの抽象メソッドとは?abstract修飾子の使い方を基礎から解説

Mirai

Javaのabstractって何のためにあるの?普通のメソッドと何が違うんだろう…。

Zetto

abstractは「サブクラスに実装を強制する仕組み」だよ。これを使いこなすと、クラス設計の意図がコードで伝えられるようになる。

この記事では、Javaの抽象メソッドとabstract修飾子の使い方を基礎から解説します。

Zettoのライタープロフィール

本記事の専門性
現役エンジニアのZettoです。Java GoldとJava Silverの資格を保有しており、JavaによるフリーランスWebアプリ開発案件を複数手がけてきました。

抽象メソッドの概念が曖昧なままだと、クラス設計で迷い続けたり、実務のコードを読んでも意味が理解できない場面が増えていきます。

この記事を読めば、抽象メソッドの仕組み・abstract修飾子の書き方・インターフェースとの使い分けまで、一通り理解できます。

ぜひ参考にしてみてください。

目次

Javaの抽象メソッドとは?abstractの基本と役割

Java抽象メソッド基本
  • 抽象メソッドの定義と特徴
  • 抽象クラス(abstract class)との関係
  • 抽象メソッドが必要な理由

抽象メソッドの定義と特徴

抽象メソッドとは、処理内容を持たずに「宣言だけ」をしたメソッドのことです。

abstract 修飾子をつけることで定義でき、メソッド本体({}ブロック)を書きません。サブクラスに「このメソッドは必ず実装してね」と強制する役割を持っています。

主な特徴は以下の通りです。

  • メソッドボディを持たない:処理は書かず、宣言のみでセミコロンで終わる
  • サブクラスに実装を強制する:オーバーライドしないとコンパイルエラーになる
  • abstractクラスの中にのみ書ける:通常のクラスには定義できない
// 抽象メソッドの例
public abstract void sound(); // {}なし・セミコロンで終わる

普通のメソッドなら {} の中に処理を書きますが、抽象メソッドはセミコロンで終わります。「処理の中身はサブクラスで考えてね」という設計図のようなイメージです。

抽象クラス(abstract class)との関係

抽象メソッドを持つクラスは、必ず抽象クラス(abstract class)にする必要があります。

抽象クラスとは、abstract修飾子をクラスにつけたものです。基本的な書き方はこちらです。

// 抽象クラスの定義
public abstract class Animal {

    // 抽象メソッド(処理内容なし)
    public abstract void sound();

    // 通常のメソッド(処理内容あり)
    public void breathe() {
        System.out.println("呼吸する");
    }
}

抽象クラスには、抽象メソッドと通常のメソッドを混在させることができます。ただし、**抽象クラス自体はインスタンス化(new)できません**。

Animal animal = new Animal(); // コンパイルエラー!

なぜインスタンス化できないかというと、抽象メソッドの処理が未定義なので、呼び出したときに何をするか決まっていないからです。Javaはそれをコンパイル時点でエラーとして検出してくれます。

抽象メソッドが必要な理由

抽象メソッドが必要な理由は、「実装漏れをコンパイル時に防げる」からです。

たとえば、犬・猫・鳥など複数の動物クラスを作るとします。それぞれに sound() メソッドが必要ですが、普通の継承だけでは実装し忘れてもプログラムは動いてしまいます。

抽象メソッドを使うと、サブクラスで実装していない場合はコンパイルエラーになります。実行する前に間違いを検出できるので、バグの予防につながるんですよね。

チーム開発では特に効果を発揮します。「このメソッドは各クラスで必ず実装してください」という設計の意図を、コードで強制できるのが抽象メソッドの本質です。

「設計の約束事をコードに落とし込む仕組み」と覚えておくと、abstract修飾子の役割がすっきり理解できるかなと思います。

Zetto

abstractは「仕様を強制するための道具」です。実装漏れをコンパイラに検出させることで、バグを未然に防げるのが最大の強みですね。

abstract修飾子の書き方と実装手順

abstract修飾子の書き方
  • 抽象クラス・抽象メソッドの基本構文
  • サブクラスでオーバーライドする方法
  • 実装を忘れた場合のコンパイルエラーの読み方

抽象クラス・抽象メソッドの基本構文

基本の書き方はシンプルです。クラスとメソッドの両方に abstract をつけることがポイントです。

// 抽象クラスの宣言
public abstract class Shape {

    // 抽象メソッド(メソッドボディなし)
    public abstract double area();

    // 通常のメソッド(メソッドボディあり)
    public void printArea() {
        System.out.println("面積:" + area());
    }
}

ここで押さえておきたい点が2つあります。

  • 抽象クラスは抽象メソッドを持たなくてもOK:abstractをクラスにつけるだけでインスタンス化を禁止できる
  • 抽象メソッドがある場合は必ず抽象クラスにする:通常クラスに書くとコンパイルエラー
// NG例:通常クラスに抽象メソッドを書くとエラー
public class Shape {
    public abstract double area(); // エラー!
}

サブクラスでオーバーライドする方法

抽象クラスを継承したサブクラスは、すべての抽象メソッドをオーバーライド(実装)する義務があります。

// Circleクラス:Shapeを継承
public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    // 抽象メソッドをオーバーライド
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

// Rectangleクラス:Shapeを継承
public class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }
}

@Override アノテーション(注釈)は必須ではありませんが、つける習慣をつけておくといいです。メソッド名のタイポ(打ち間違い)をコンパイラが検出してくれるからです。

実際に呼び出す場面はこちらです。

public class Main {
    public static void main(String[] args) {
        Shape circle    = new Circle(5.0);
        Shape rectangle = new Rectangle(4.0, 6.0);

        circle.printArea();    // 面積:78.53...
        rectangle.printArea(); // 面積:24.0
    }
}

Shape型の変数に CircleRectangle のインスタンスを代入して使えます。これを**ポリモーフィズム(多態性)**と呼び、abstract を使う大きなメリットのひとつです。

実装を忘れた場合のコンパイルエラーの読み方

抽象メソッドのオーバーライドを忘れると、以下のエラーが出ます。

Circle is not abstract and does not override abstract method area() in Shape

日本語に訳すと「CircleはabstractじゃないのにShapeの area() をオーバーライドしていない」という意味です。

このエラーが出たときの対処法は2つあります。対処法①はサブクラスに area() メソッドを実装する(通常はこちら)、対処法②はサブクラスも abstract クラスにする(さらに継承先に実装を委ねる)です。

Zetto

実務では対処法①がほとんどです。エラーメッセージを丁寧に読む癖をつけると、どこで何が足りないかすぐ特定できるようになります。

抽象クラスとインターフェースの違いと使い分け

抽象クラス/インターフェース違い
  • 抽象クラスとインターフェースの主な違い
  • どちらを使うべきか?判断基準
  • Java Silver・Gold試験の頻出パターン

抽象クラスとインターフェースの主な違い

抽象クラスとインターフェースは似ているようで、設計の目的が異なります。

項目抽象クラスインターフェース
キーワードabstract classinterface
継承・実装extends(1つだけ)implements(複数可)
フィールド定義できる定数のみ(public static final
コンストラクタ持てる持てない
メソッド抽象・通常どちらも書ける抽象+Java 8以降はdefault/staticも可
主な用途「is-a」関係(〜は〜の一種)「can-do」関係(〜は〜できる)

インターフェースはJava 8以降、defaultメソッドやstaticメソッドも書けるようになったため、できることの差は縮まっています。ただ、設計の意図としては表の「用途」の違いが本質です。

どちらを使うべきか?判断基準

判断基準は「継承関係か、能力の付与か」で決まります。

抽象クラスを使うべきケース

複数のサブクラスに共通の「ベース」を提供したいときに使います。

  • 共通のフィールドを複数のサブクラスで共有したい
  • 一部の処理をまとめてサブクラスにそのまま使わせたい
  • 「犬は動物の一種」「円は図形の一種」のような継承関係を表したい

インターフェースを使うべきケース

クラスに「〇〇できる」という能力を後付けしたいときに使います。

  • Javaは1クラスしか継承できないが、複数の機能を付与したい
  • 「比較できる(Comparable)」「実行できる(Runnable)」のような能力を表したい
  • 異なる継承ツリーのクラスに共通の振る舞いを持たせたい

実務でのイメージとして、「テンプレート(ひな型)を提供したいなら抽象クラス、契約(仕様)を定義したいならインターフェース」と覚えると判断しやすいかなと思います。

実務でのabstractの使われ方【Spring Boot活用例】

Spring Bootでのabstract活用
  • テンプレートメソッドパターンとは
  • Spring Bootでの活用シーン
  • 抽象クラスを使ったクラス設計の考え方

テンプレートメソッドパターンとは

テンプレートメソッドパターンとは、「処理の大枠(骨格)を抽象クラスに定義し、具体的な処理だけをサブクラスに任せる」設計パターンです。

デザインパターン(よく使われる設計の型)のひとつで、abstractを使う最も代表的な実務上の使い方です。

// 抽象クラス:処理の骨格を定義
public abstract class DataExporter {

    // テンプレートメソッド(finalで手順を固定)
    public final void export() {
        fetchData();   // 1. データ取得(共通)
        formatData();  // 2. データ整形(サブクラスに任せる)
        outputData();  // 3. データ出力(共通)
    }

    protected abstract void formatData(); // サブクラスが実装

    private void fetchData() {
        System.out.println("データを取得しました");
    }

    private void outputData() {
        System.out.println("データを出力しました");
    }
}

// CSV出力クラス
public class CsvExporter extends DataExporter {
    @Override
    protected void formatData() {
        System.out.println("CSV形式に整形しました");
    }
}

// Excel出力クラス
public class ExcelExporter extends DataExporter {
    @Override
    protected void formatData() {
        System.out.println("Excel形式に整形しました");
    }
}

「エクスポートの手順は同じだけど、フォーマットだけ変えたい」というシチュエーションにぴったりの設計です。処理の流れを統一しつつ、変化する部分だけをサブクラスに委ねられます。

Spring Bootでの活用シーン

JavaでWebアプリを開発するフレームワークとしてよく使われる Spring Boot でも、abstractの考え方は随所に活用されています。

共通の基底サービスクラスを作る

実務で頻繁に見かける使い方が、共通処理を持つ基底サービスクラスです。

// 共通の基底サービスクラス
public abstract class BaseService {

    // 共通のバリデーション処理
    protected void validate(T entity) {
        if (entity == null) {
            throw new IllegalArgumentException("エンティティがnullです");
        }
    }

    // サブクラスに任せる処理
    public abstract T findById(Long id);
    public abstract T save(T entity);
}

// ユーザーサービス
@Service
public class UserService extends BaseService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User findById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    @Override
    public User save(User user) {
        validate(user); // 基底クラスの共通処理を使う
        return userRepository.save(user);
    }
}

この設計だと、バリデーションなど共通の処理を基底クラスにまとめておけます。各サービスクラスで同じコードを何度も書く必要がなくなり、コードの重複(DRY原則違反)を防げます。

抽象クラスを使ったクラス設計の考え方

抽象クラスを使うべきかどうかの判断は、「サブクラス間で共通する処理があるか」で決まります。

設計の流れはこうなります。

  • Step 1:複数のクラスに共通する処理を探す
  • Step 2:共通処理を抽象クラスにまとめる
  • Step 3:各クラス固有の処理を抽象メソッドに切り出す
  • Step 4:サブクラスで抽象メソッドを実装する

逆に言うと、共通処理がないなら無理にabstractを使う必要はありません。「abstractを使うこと」が目的にならないよう、設計の意図を先に考えるのが大切です。

Zetto

実務でのabstractは「コードの重複を減らして、設計の意図を伝える」ために使います。学習段階ではピンとこないかもしれませんが、チーム開発を経験するとその価値が実感できますよ。

Javaの抽象メソッドの演習問題

抽象メソッド演習問題
  • 演習問題1:動物クラスを抽象クラスで設計する
  • 演習問題2:抽象クラスとインターフェースを使い分ける
  • 演習問題3:テンプレートメソッドパターンを実装する

演習問題1:動物クラスを抽象クラスで設計する

問題:以下の条件を満たすクラスを実装してください。

  • Animal 抽象クラスを作成し、sound() を抽象メソッドとして定義する
  • eat() は「食事する」と出力する通常のメソッドとして定義する
  • Dog クラスと Cat クラスを作成し、Animal を継承する
  • Dogsound() は「ワン!」、Catsound() は「ニャー!」と出力する
  • Main クラスで両方のインスタンスを作り、sound()eat() を呼び出す

解答例:

// 抽象クラス
public abstract class Animal {
    public abstract void sound();

    public void eat() {
        System.out.println("食事する");
    }
}

// 犬クラス
public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("ワン!");
    }
}

// 猫クラス
public class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("ニャー!");
    }
}

// メインクラス
public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.sound(); // ワン!
        dog.eat();   // 食事する
        cat.sound(); // ニャー!
        cat.eat();   // 食事する
    }
}

解説:Animal 型の変数に DogCat のインスタンスを代入できる点がポイントです。sound() を呼び出すと、実際のオブジェクトの型に応じた処理が実行されます(ポリモーフィズム)。eat() は抽象クラスに実装済みなので、両クラスで共通して使えます。

演習問題2:抽象クラスとインターフェースを使い分ける

問題:以下の要件を満たすクラス設計を実装してください。

  • Vehicle 抽象クラスを作成し、フィールド name(車両名)と抽象メソッド move() を定義する
  • Chargeable インターフェースを作成し、charge() メソッドを定義する
  • Car クラス(Vehicle 継承)と ElectricCar クラス(Vehicle 継承 + Chargeable 実装)を作成する
  • Carmove() は「ガソリンで走る」、ElectricCarmove() は「電気で走る」と出力する
  • ElectricCarcharge() は「充電する」と出力する

解答例:

// 抽象クラス
public abstract class Vehicle {
    protected String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public abstract void move();
}

// インターフェース
public interface Chargeable {
    void charge();
}

// 通常の車
public class Car extends Vehicle {
    public Car(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + ":ガソリンで走る");
    }
}

// 電気自動車
public class ElectricCar extends Vehicle implements Chargeable {
    public ElectricCar(String name) {
        super(name);
    }

    @Override
    public void move() {
        System.out.println(name + ":電気で走る");
    }

    @Override
    public void charge() {
        System.out.println(name + ":充電する");
    }
}

Vehicle は「車両の一種である」という継承関係を表す抽象クラス、Chargeable は「充電できる」という能力を表すインターフェースです。電気自動車だけが「充電できる」ので、その能力をインターフェースで付与しています。この使い分けが、実務でのクラス設計の基本になります。

演習問題3:テンプレートメソッドパターンを実装する

問題:レポート生成処理をテンプレートメソッドパターンで実装してください。

  • ReportGenerator 抽象クラスを作成する
  • generate() メソッドを final で定義し、「① データ収集 → ② フォーマット → ③ 出力」の順で呼び出す
  • format() だけを抽象メソッドにする
  • PdfReport クラスと HtmlReport クラスを作成し、それぞれ異なるフォーマットを出力する

解答例:

// 抽象クラス(テンプレート)
public abstract class ReportGenerator {

    // 手順を固定するためfinalをつける
    public final void generate() {
        collectData();
        format();
        output();
    }

    private void collectData() {
        System.out.println("データを収集しました");
    }

    protected abstract void format(); // サブクラスが実装

    private void output() {
        System.out.println("レポートを出力しました");
    }
}

// PDF形式
public class PdfReport extends ReportGenerator {
    @Override
    protected void format() {
        System.out.println("PDF形式にフォーマットしました");
    }
}

// HTML形式
public class HtmlReport extends ReportGenerator {
    @Override
    protected void format() {
        System.out.println("HTML形式にフォーマットしました");
    }
}

// メインクラス
public class Main {
    public static void main(String[] args) {
        ReportGenerator pdf  = new PdfReport();
        ReportGenerator html = new HtmlReport();

        pdf.generate();
        System.out.println("---");
        html.generate();
    }
}

generate()final をつけているのは、処理の骨格を変えられないようにするためです。変化する部分(フォーマット)だけを抽象メソッドで切り出すことで、新しいレポート形式を追加したいときは format() を実装するだけで対応できます。拡張しやすく、変更に強い設計がテンプレートメソッドパターンの強みです。

Zetto

演習問題はまず自分で手を動かしてから解答を見ることをおすすめします。コピー&ペーストだと理解したつもりになりやすいので、一度自分でタイプしてみるだけで定着度がまったく変わりますよ。

abstract修飾子をマスターして、クラス設計の力を上げよう

abstract修飾子マスター術

この記事では、Javaの抽象メソッドとabstract修飾子について解説しました。

重要なポイントをまとめます。

  • 抽象メソッド:処理内容を持たず、サブクラスに実装を強制するメソッド
  • 抽象クラス:抽象メソッドを含むクラス。インスタンス化できない
  • インターフェースとの違い:共通の基盤を作るなら抽象クラス、能力を付与するならインターフェース
  • 実務での使い方:テンプレートメソッドパターンやSpring Bootの基底サービスクラスなど

abstract修飾子は、最初は「なぜこんな面倒なことを?」と感じるかもしれません。でも実務でチーム開発を経験すると、設計の意図をコードで伝えられるこの仕組みの価値がわかってきます。

Javaのオブジェクト指向をさらに深めたい方は、インターフェースの書き方・使い方もあわせて身につけておくとよいかなと思います。クラス設計の選択肢が広がり、より柔軟なコードが書けるようになります。

Mirai

なるほど!abstractって「サブクラスに実装を強制する仕組み」なんだね。テンプレートメソッドパターンの例がすごくわかりやすかった!

Zetto

abstractを使いこなせるようになると、クラス設計の幅がぐっと広がるよ。演習問題も繰り返し解いて、自分のものにしてみてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次