Javaでabstractを完全に理解するたった11のステップ

Java abstract キーワード ガイド サンプルコードJava
この記事は約21分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、基礎知識があれば初心者にも理解していただけるように、常に解説内容のわかりやすさや記事の品質に注力しております。不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めばJavaのabstractを完全に理解できるようになります。

多くの初心者がプログラミングの世界でつまずくポイントが、抽象的な概念をどう具体的なコードに落とし込むか、ですよね。

特にJavaでよく使われるabstractキーワードはその代表格。このキーワードについて理解できれば、Javaのプログラミングがぐっと楽になります。

今回の記事ではJavaでよく使われるabstractキーワードに焦点を当て、その使い方から応用、さらには注意点まで、実際のサンプルコードを交えながら徹底解説していきます。

初心者から上級者まで、Javaのabstractに関する全てがこの記事一つで理解できるようになっています。

●Javaとは

○Javaの基本概念

Javaは、1990年代に登場したプログラミング言語で、一度書いたコードが様々なプラットフォームで動作する特性を持っています。

これはJavaの持つ「Write Once, Run Anywhere(一度書けばどこでも動く)」の理念から来ています。

○Javaが広く使用される理由

多くの企業で採用されている背景には、Javaが持ついくつかの特長があります。まず、豊富なライブラリとフレームワークが存在し、開発を効率化できます。

また、オブジェクト指向プログラミングを完全にサポートしているため、大規模な開発にも対応可能です。

さらには、セキュリティ面でも優れており、Webアプリケーションから企業のビジネスロジックまで幅広い用途で使用されています。

●abstractキーワードとは

プログラミングの世界には、具体的な実装よりも、それを支える抽象的な設計が非常に重要な場合があります。

Javaでこの「抽象」を表現するための一つの手段が、abstractキーワードです。

ここでは、abstractキーワードの基本的な意味と、その必要性について解説します。

○abstractの基本的な意味

abstractキーワードは、Javaで抽象クラスや抽象メソッドを定義するための修飾子です。

抽象クラスとは、具体的な実装が一部省かれ、サブクラスによってその詳細が埋められるべきクラスのことを指します。

同様に、抽象メソッドは、そのメソッドの実装が抽象クラスには存在せず、サブクラスによって提供されるべきメソッドです。

○abstractが必要な場合

抽象クラスや抽象メソッドが必要になる一般的なケースは、いくつかあります。

  1. 共通の処理を持つが、一部の処理がサブクラスごとに異なる場合
  2. 同じような機能を持つクラスが複数存在し、その共通部分を一元管理したい場合
  3. 柔軟な設計で未来の拡張性を確保したい場合

これらの状況でabstractキーワードを活用することで、効率的かつ柔軟なプログラミングが可能になります。

●abstractクラスの作り方

Javaで抽象クラスを使う場面は多々ありますが、その作成方法と適用ケースをしっかり把握することが大事です。

ここでは、そのポイントを押さえたいと思います。

サンプルコードも用いながら、具体的な手法を解説します。

○サンプルコード1:基本的なabstractクラスの作成

まずは最も基本的な形から見ていきましょう。

// 動物を表す抽象クラス
public abstract class Animal {
    // 抽象メソッド(具体的な実装がない)
    public abstract void makeSound();
}

このサンプルコードでは、Animalという名前の抽象クラスを作成しています。

抽象メソッドとしてmakeSoundが定義されていますが、その具体的な実装はここでは表されていません。

この抽象クラスを用いて、犬や猫、鳥などの具体的な動物のクラスを作成できます。

それぞれの動物クラスでmakeSoundメソッドを実装することで、その動物固有の鳴き声を出力できるようにします。

○サンプルコード2:abstractメソッドを持つクラス

次に、具体的なメソッドと抽象メソッドが共存するケースを見てみましょう。

// 乗り物を表す抽象クラス
public abstract class Vehicle {
    // 具体的なメソッド
    public void start() {
        System.out.println("エンジンを始動");
    }

    // 抽象メソッド
    public abstract void run();
}

このサンプルでは、Vehicleという抽象クラスにstartという具体的なメソッドと、runという抽象メソッドがあります。

startメソッドはエンジンを始動するという一般的な動作を表現しています。

一方で、runは抽象メソッドなので、具体的な走り方はサブクラスで定義する必要があります。

○サンプルコード3:abstractクラスを継承する

最後に、抽象クラスを継承してサブクラスを作成する例を紹介します。

// Animalクラスを継承するDogクラス
public class Dog extends Animal {
    // 抽象メソッドをオーバーライド
    public void makeSound() {
        System.out.println("ワンワン");
    }
}

DogクラスはAnimalクラスを継承しており、抽象メソッドmakeSoundをオーバーライドして具体的な実装できます。

この実装により、「ワンワン」という犬特有の鳴き声が出力されるようになっています。

●abstractメソッドの作り方

abstractクラスの次に注意を向けるべきはabstractメソッドです。

abstractメソッドは、具体的な処理を持たず、そのシグネチャ(メソッド名、引数の型、戻り値の型)だけを定義するメソッドです。

これにより、継承先のクラスでこのメソッドの実装を強制することができます。

ここでは、abstractメソッドの基本的な形や、その用途、実装例について詳しく説明します。

○サンプルコード4:abstractメソッドの基本形

Javaでabstractメソッドを定義するには、次のような形になります。

public abstract class Shape {
    // abstractメソッドの定義
    public abstract double area();
}

上記のコードでは、Shapeという抽象クラス内で、areaというabstractメソッドが定義されています。

このareaメソッドは面積を計算するメソッドであり、戻り値としてdouble型の数値を返す契約がされています。

具体的な計算方法は、この抽象クラスを継承する各図形クラス(例:Circle、Square)で定義されることになります。

○サンプルコード5:具体的なメソッドを持つabstractクラス

abstractクラスはabstractメソッドだけでなく、具体的なメソッドも持つことができます。

public abstract class Printer {
    // 具体的なメソッド
    public void turnOn() {
        System.out.println("プリンターを起動します。");
    }

    // abstractメソッド
    public abstract void print();
}

このコードでは、Printerという抽象クラスに、turnOnという具体的なメソッドとprintというabstractメソッドが共存しています。

turnOnメソッドはプリンターを起動する一般的な動作を表現しています。

一方でprintは抽象メソッドで、具体的な印刷処理は継承先で定義します。

このPrinterクラスを継承すると、turnOnメソッドはそのまま利用できますが、printメソッドについては各サブクラスで具体的な処理を実装する必要があります。

例えば、Printerクラスを継承したInkjetPrinterクラスを作成する場合、次のようにprintメソッドを実装できます。

public class InkjetPrinter extends Printer {
    // abstractメソッドをオーバーライド
    public void print() {
        System.out.println("インクジェットプリンターで印刷します。");
    }
}

このInkjetPrinterクラスを使用すると、「インクジェットプリンターで印刷します。」というメッセージが出力されることになります。

●abstractクラスとインターフェースの違い

Javaにおいて、abstractクラスとインターフェースはよく比較される概念です。

どちらも継承と多態性を実現するためのメカニズムを提供していますが、使い道や制約が異なります。

ここでは、それぞれの特性と使い分けのポイントに焦点を当てて解説します。

○インターフェースとは

インターフェースは、抽象メソッドと静的な(またはデフォルトの)メソッドだけを持つことができます。

インスタンス変数を持つことはできません。

Javaでは、interfaceキーワードを用いて定義します。

// インターフェースの例
interface Animal {
    void makeSound(); // 抽象メソッド
    default void eat() {
        System.out.println("食事をします");
    }
}

このAnimalインターフェースでは、makeSoundという抽象メソッドと、eatというデフォルトメソッドが定義されています。

抽象メソッドは必ずオーバーライドする必要がありますが、デフォルトメソッドはオーバーライドしなくても使用できます。

○abstractクラスとインターフェースの使い分け

abstractクラスとインターフェースはそれぞれに適した用途があります。

それでは、その主な使い分けのポイントを紹介します。

  • 状態を持つ場合はabstractクラスを使用:abstractクラスではフィールド変数を持つことができますが、インターフェースでは持つことができません。
  • 複数継承が必要な場合はインターフェースを使用:Javaではクラスの多重継承は許されていませんが、インターフェースならば複数継承が可能です。
  • APIとしての一貫性を保ちたい場合はインターフェースを使用:インターフェースは後からメソッドを追加することが容易であるため、ライブラリなどで一貫性を保つために有用です。
  • 実装の詳細が共有されるべき場合はabstractクラスを使用:共通の処理を一元管理するためには、abstractクラスでその処理を実装し、継承して使用する方が適しています。

このように、abstractクラスとインターフェースはそれぞれ特定のシナリオにおいて有用です。

どちらを使用するかはプロジェクトの要件や設計方針によって決定するべきです。

●abstractの詳細な使い方

基本的なabstractクラスとabstractメソッドの作成方法について理解したところで、より深いレベルでの使い方を探っていきましょう。

高度な使い方をマスターすることで、より効率的なプログラムを作成する扉が開かれます。

○サンプルコード6:複数のabstractメソッドを持つ場合

Javaでは、一つのabstractクラスが複数のabstractメソッドを持つことができます。

その場合、継承する全ての具体クラスで、これらのabstractメソッドをオーバーライドする必要があります。

例を見て理解を深めましょう。

// 複数のabstractメソッドを持つ抽象クラス
abstract class Vehicle {
    // 抽象メソッド1
    abstract void startEngine();
    // 抽象メソッド2
    abstract void stopEngine();
}

class Car extends Vehicle {
    // startEngineメソッドのオーバーライド
    @Override
    void startEngine() {
        System.out.println("エンジンを始動");
    }
    // stopEngineメソッドのオーバーライド
    @Override
    void stopEngine() {
        System.out.println("エンジンを停止");
    }
}

このサンプルコードでは、Vehicleという名前のabstractクラスにstartEnginestopEngineという2つのabstractメソッドを定義しています。

継承先の具体クラスであるCarクラスでは、これら2つのabstractメソッドがオーバーライドされています。

このコードを実行すると、特に出力はありませんが、CarクラスのインスタンスでstartEngineメソッドやstopEngineメソッドを呼び出すことで、「エンジンを始動」と「エンジンを停止」と表示されます。

○サンプルコード7:内部クラスでabstractを使用する

Javaでは、内部クラスでもabstractを使用することができます。

これにより、外部クラスが一定の規約(インターフェース)を持つように設計できます。

// 内部クラスでabstractを使用する例
public class OuterClass {
    abstract class InnerAbstractClass {
        abstract void innerMethod();
    }

    class InnerConcreteClass extends InnerAbstractClass {
        @Override
        void innerMethod() {
            System.out.println("内部クラスのメソッド");
        }
    }
}

このコードは、OuterClassという名前のクラス内にInnerAbstractClassというabstractクラスを定義しています。

この抽象内部クラスには、innerMethodという抽象メソッドがあり、これを継承したInnerConcreteClassでオーバーライドしています。

このコードを実行すると、特に何も表示されませんが、InnerConcreteClassのインスタンスでinnerMethodを呼び出すと、「内部クラスのメソッド」と表示されます。

●abstractの応用例

abstractクラスやabstractメソッドが基本的にどのように動作するのか、一通りの理解が深まったと思います。しかし、これだけではabstractの真価は発揮されません。

ここでは、具体的な応用例をいくつか挙げ、その使い方を詳細に解説します。

○サンプルコード8:設計パターンとしてのabstract

抽象クラスは設計パターン、特に「テンプレートメソッドパターン」でよく用いられます。

これは一部の手続きがサブクラスによって異なるような場合に便利です。

// テンプレートメソッドパターンの一例
abstract class Beverage {
    // テンプレートメソッド
    final void prepare() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    void boilWater() {
        System.out.println("水を沸騰させる");
    }

    abstract void brew(); // 抽象メソッド

    void pourInCup() {
        System.out.println("カップに注ぐ");
    }

    abstract void addCondiments(); // 抽象メソッド
}

class Tea extends Beverage {
    @Override
    void brew() {
        System.out.println("紅茶を淹れる");
    }

    @Override
    void addCondiments() {
        System.out.println("レモンを追加");
    }
}

class Coffee extends Beverage {
    @Override
    void brew() {
        System.out.println("コーヒーを淹れる");
    }

    @Override
    void addCondiments() {
        System.out.println("砂糖とミルクを追加");
    }
}

このサンプルコードではBeverageという抽象クラスを定義しています。

この抽象クラスにはprepareというメソッドがあり、これが「テンプレートメソッド」となっています。

このメソッド内で抽象メソッドであるbrewaddCondimentsが呼び出されています。

サブクラスであるTeaCoffeeでは、これらの抽象メソッドが具体的にどのように動作するのかを定義しています。

このコードを実行した際、TeaまたはCoffeeprepareメソッドを呼び出すと、そのクラスに応じた一連の手続きが実行されます。

○サンプルコード9:abstractを使った高度な計算

高度な計算ロジックを有するプログラムでも、abstractクラスは非常に有用です。

abstract class Calculator {
    abstract int operation(int a, int b);

    int calculate(int a, int b) {
        return operation(a, b);
    }
}

class Addition extends Calculator {
    @Override
    int operation(int a, int b) {
        return a + b;
    }
}

class Subtraction extends Calculator {
    @Override
    int operation(int a, int b) {
        return a - b;
    }
}

この例では、Calculatorという名前の抽象クラスにoperationという抽象メソッドが定義されています。

この抽象メソッドは、サブクラスで具体的な計算ロジックが実装されます。

AdditionSubtractionという具体クラスでは、operationメソッドがそれぞれ加算と減算にオーバーライドされています。

このコードを実行すると、AdditionSubtractionのインスタンスでcalculateメソッドを呼び出すと、加算または減算の結果が得られます。

○サンプルコード10:抽象クラスとデータベースの連携

データベースと連携する際も、抽象クラスは役立ちます。

例えば、データベースに接続する共通の処理を抽象クラスで定義し、その抽象クラスを継承した具体クラスで各データベースの独自の処理を実装する、といった使い方が考えられます。

ただし、この例では疑似的なコードを使います。

abstract class DatabaseConnector {
    void connect() {
        System.out.println("データベースに接続");
    }

    abstract void query(String sql); // SQLクエリの実行はサブクラスで
}

class MySQLConnector extends DatabaseConnector {
    @Override
    void query(String sql) {
        System.out.println("MySQLでクエリを実行: " + sql);
    }
}

class PostgreSQLConnector extends DatabaseConnector {
    @Override
    void query(String sql) {
        System.out.println("PostgreSQLでクエリを実行: " + sql);
    }
}

このサンプルコードでは、DatabaseConnectorという抽象クラスがあります。

このクラスには、connectメソッドでデータベースに接続する処理と、queryという抽象メソッドが定義されています。

この抽象メソッドは、継承する各データベース専用クラスで具体的なクエリ処理が定義されます。

このコードを実行すると、MySQLConnectorPostgreSQLConnectorのインスタンスでqueryメソッドを呼び出すと、それぞれのデータベース専用のクエリ処理が行われます。

●注意点と対処法

abstractを使いこなせば、プログラミングが効率的で柔軟になります。

しかし、適切な使い方をしないと、コードの可読性や保守性が落ちる可能性もあります。

そこでここでは、abstractを使用する際の注意点とその対処法について解説します。

○abstractの落とし穴

  • 抽象クラスはインスタンス化できない:抽象クラス自体はnew演算子でインスタンス化することができません。これは初心者がよく陥るトラップの一つです。
  • 抽象メソッドのオーバーライド:抽象メソッドは、そのサブクラスで必ずオーバーライドしなければならない。もしオーバーライドを忘れるとコンパイルエラーが発生します。
  • 具象クラス内での抽象メソッドの宣言:具象クラス(abstractを指定していないクラス)内で抽象メソッドを宣言することはできません。

○サンプルコード11:注意すべきポイントと対処法

下記のサンプルコードでは、上述した「抽象メソッドのオーバーライド」の注意点と対処法を紹介します。

// 抽象クラスの定義
abstract class Animal {
    abstract void makeSound();
}

// サブクラスで抽象メソッドをオーバーライド
class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("ワンワン");
    }
}

// 抽象メソッドをオーバーライドしていないサブクラス(コンパイルエラー)
// class Cat extends Animal {
//    コンパイルエラーとなるため、この部分はコメントアウト
// }

このサンプルコードは、抽象クラスAnimalと、その抽象メソッドmakeSoundを持っています。

サブクラスDogはこの抽象メソッドをちゃんとオーバーライドしています。

よって、このクラスは問題ありません。

しかし、Catクラスは抽象メソッドをオーバーライドしていないため、コンパイルエラーが発生します。

このように、抽象メソッドが存在する場合、そのサブクラスでは必ずオーバーライドする必要があります。

このコードを実行すると、DogクラスのインスタンスでmakeSoundメソッドを呼び出すと、コンソールに「ワンワン」と出力されます。

一方で、Catクラスはコンパイルエラーが出るため、実行すらできません。

●カスタマイズ方法

abstractクラスやメソッドはJavaプログラミングにおいて、多くの場面で使われます。

しかし、これらをカスタマイズしてより高度なコーディングを行いたい場合もあるでしょう。

ここでは、abstractクラスを拡張する際のテクニックと、コードの可読性を高める方法を詳しく解説します。

○サンプルコード12:abstractクラスを拡張する際のテクニック

抽象クラスは拡張が容易であり、それが一つの強みです。

具象クラスを作成する際に抽象クラスを拡張することで、新しい機能を追加することができます。

// 抽象クラスAnimalを定義
abstract class Animal {
    abstract void makeSound();
}

// Animalクラスを拡張してWildAnimalクラスを作成
abstract class WildAnimal extends Animal {
    // 新たにterritoryメソッドを追加
    abstract void territory();
}

// WildAnimalクラスを具象化するLionクラス
class Lion extends WildAnimal {
    @Override
    void makeSound() {
        System.out.println("ガオー");
    }

    @Override
    void territory() {
        System.out.println("サバンナ");
    }
}

このコードは抽象クラスAnimalに対して、新たな抽象クラスWildAnimalを作成し、その中で新しい抽象メソッドterritoryを追加しています。

具象クラスLionでは、AnimalWildAnimalの両方の抽象メソッドをオーバーライドしています。

このコードを実行する場合、LionクラスのインスタンスでmakeSoundメソッドとterritoryメソッドを呼び出すと、「ガオー」と「サバンナ」という文字列がそれぞれ出力されます。

○サンプルコード13:コードの可読性を高めるテクニック

コードの可読性を高めるには、メソッド名や変数名を明確にし、必要な場合は適切なコメントを加えると良いです。

さらに、抽象クラスやメソッドにJavadocコメントを追加することも有用です。

/**
 * Animalクラスは動物の基本的な特性を定義します。
 */
abstract class Animal {
    /**
     * 動物の鳴き声を出力するメソッドです。
     */
    abstract void makeSound();
}

このサンプルコードでは、Javadocコメントを用いて抽象クラスAnimalとその中の抽象メソッドmakeSoundに対する説明を加えています。

これにより、他の開発者がコードを読む際の理解を助けます。

Javadocコメントは特別なコメントであり、IDEやドキュメント生成ツールがこれを読み取って、APIドキュメントを自動生成することが可能です。

まとめ

Javaでのabstractキーワード使用に関する幅広いテーマにわたって、基礎から応用、カスタマイズ方法に至るまでを網羅的に解説しました。

初心者が抽象クラスや抽象メソッドを理解するための基本的なガイドラインから、既に経験がある方が更なるスキルアップを目指すための高度なテクニックまで、多角的に要点を押さえています。

抽象クラスや抽象メソッドは、Javaプログラミングの非常に強力な概念であり、設計段階での柔軟性や拡張性を高める手段として重要です。

しかし、その機能の強力さゆえに、うまく活用しないとコードが複雑になる可能性もあります。

そのような問題を避けるためにも、本記事で解説した各種テクニックや注意点は大いに参考になるでしょう。

この記事を通して、abstractキーワードの真の力を理解し、Javaプログラミングのスキルを高める一助となれば幸いです。