Java オブジェクト指向設計:抽象化とポリモーフィズムの徹底解説

抽象クラスと抽象メソッド

abstract キーワードは、クラスまたはメソッドに対して適用される修飾子です。このキーワードを使用して宣言されたものは、完全な定義を持たず、サブクラスによる実装を必要とします。

  • 制約事項: final, static, private と同時に使用することはできません。また、抽象メソッドを直接実装した状態で定義されるため、オーバーライド(再定義)の対象となります。
  • 抽象クラスの特徴:
    1. インスタンス化(new演算子)による生成は禁止されています。
    2. コンストラクタが含まれますが、これは直接オブジェクトを作成するために使われるのではなく、継承先であるサブクラスの初期化時に呼び出されます。
    3. メンバー変数、通常メソッド、静的メソッド、コンストラクタ、そして抽象メソッドの混合が可能です。
    4. 他のクラスからの継承のみを目的としています。
  • 利用シーン: 共通の振る舞いを親クラスに定義したいものの、具体的な動作が子クラスによって異なる場合や、処理の骨組みだけを規定し、詳細を委任する場合に適しています。

抽象メソッドの詳細

抽象メソッドとは、実装(メソッドボディ)を持たないメソッド宣言です。これを記述すると、その親クラスは自動的に抽象クラスとして扱われます。非抽象のサブクラスは、全ての抽象メソッドを実装しなければなりません。もし一部でも実装しない場合は、そのサブクラス自体も抽象クラスとして宣言する必要があります。

実装例:従業員管理システム

「従業員」を抽象基盤とし、「正社員」と「契約社員」を拡張するモデルを考えます。全員に共通の「業務実行」がありますが、内容は異なります。

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

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

    // 抽象メソッド:各社員の独自の業務内容
    public abstract void executeJob();
}

// サブクラス:正社員
public class RegularEmployee extends Employee {
    public RegularEmployee(String name) {
        super(name);
    }

    @Override
    public void executeJob() {
        System.out.println(name + " が定常業務に従事します。");
    }
}

// サブクラス:契約社員(さらに詳細化)
public class ContractEmployee extends Employee {
    private String contractPeriod;

    public ContractEmployee(String name, String contractPeriod) {
        super(name);
        this.contractPeriod = contractPeriod;
    }

    @Override
    public void executeJob() {
        System.out.println(name + " は契約期間中に専門業務を行います。");
    }
}

インタフェースの活用

インタフェースは、特定の機能の実装を保証するための契約(ルール)を定義する参照タイプです。JDK 1.7 では抽象メソッドと静的定数のみでしたが、JDK 1.8以降ではデフォルトメソッドや静的メソッドの記述も可能になりました。

  • 構造的な違い: クラス間の関係は単一継承ですが、インターフェースとの関係は多重実装が可能です。これにより、単一の親クラスしかない制約を緩和できます。
  • メンバのデフォルト属性:
    • メソッド:public abstract (ただし実装時には省略可能)
    • 変数:public static final (定数として扱い、値の変更不可)
    • デフォルトメソッド:public (実装を提供)

サービス層のデザイン例

データ管理の規約をインタフェースで定義し、複数通りの実装を用意する構成です。

// 規約の定義
public interface DataStorageService {
    // 定数定義
    int MODE_READ = 1;
    int MODE_WRITE = 2;

    // 抽象メソッド(必須実装)
    void save(Object data);
    Object load(int id);

    // JDK8以降:デフォルト実装(オプション)
    default void logProcess() {
        System.out.println("プロセスログを記録");
    }
    
    // 静的ユーティリティメソッド
    static boolean isValidFormat(String key) {
        return key != null && !key.isEmpty();
    }
}

// 実際の実装クラス
public class DatabaseStorage implements DataStorageService {
    @Override
    public void save(Object data) {
        // データベースへの保存ロジック
        System.out.println("DB にデータを保存しました。");
    }

    @Override
    public Object load(int id) {
        return null; // 簡略化のため
    }
    
    // インタフェースのデフォルトメソッドはそのまま利用可能
}

ポリモーフィズム(多態性)

多態性は、同一の操作に対する対象の挙動が異なることを可能にする仕組みです。基底クラスの参照変数を用いて派生クラスオブジェクトを扱うことで、柔軟なコード設計が実現できます。

  • クラス多態: 子クラスのインスタンスを親クラスの変数に代入(アップキャスト)。
  • インタフェース多态: 実装クラスインスタンスをインタフェース変数に代入。
  • OCP 原則(開放・閉鎖原則): 既存コードを変更せずに新しい機能を追加できるように設計することを目指します。これが多態性の最大の利点の一つです。

輸送手段のシミュレーション

abstract class Transport {
    public abstract void move();
}

class Car extends Transport {
    public void move() {
        System.out.println("自動車で移動");
    }
}

class Plane extends Transport {
    public void move() {
        System.out.println("飛行機で移動");
    }
}

public class LogisticsSystem {
    // 具体的な交通手段を受け取らず、抽象型で受け取る
    public void transportGoods(Transport t) {
        t.move();
    }
    
    public static void main(String[] args) {
        LogisticsSystem sys = new LogisticsSystem();
        // 多態的な呼び出し:引数の型に応じて挙動が変わる
        sys.transportGoods(new Car());
        sys.transportGoods(new Plane());
    }
}

オブジェクトの型変換(Casting)

継承関係にあるクラス間での型変換には安全性の確認が必要です。

1. 暗黙的変換(アップキャスト)

サブクラス型からスーパークラス型への変換です。これは常に安全であり、明示的なキャスト演算子は不要です。しかし、元々の持つ独自メソッドへのアクセスはできなくなります。

String str = "Text";
Object obj = str; // Object 型の参照に文字列オブジェクトを格納

2. 明示的変換(ダウンキャスト)

スーパークラス型からサブクラス型への変換です。运行时エラー(ClassCastException)のリスクがあるため、必ず型チェックを行う必要があります。

Object obj = new Customer();

// 安全なキャストの実施パターン
if (obj instanceof Customer) {
    Customer cust = (Customer) obj;
    cust.processAccount();
} else {
    // 予期しない型の場合の処理
    System.out.println("顧客オブジェクトではありません");
}

内部クラスの種類と特性

外部クラスの中に定義されるクラスを内部クラスと呼び、用途によっていくつかの形態があります。

  • メンバー内部クラス: 外部クラスのインスタンスに関連付けられます。外部クラスの全てのメンバにアクセス可能です。インスタンス生成には外部クラスのインスタンスが必要になります。
    Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();
  • スタティック内部クラス: 外部クラスの静的メンバとして扱われます。外部インスタンスなしで生成可能ですが、外部クラスのインスタンスメンバにはアクセスできません。
    Outer.StaticInner inner = new Outer.StaticInner();
  • ローカル内部クラス: メソッド体内に定義されます。メソッドのスコープ外では使用でき、ローカル変数を使う場合は final またはイミュータブルな変数である必要があります。
  • アンノニマス内部クラス: 名前を持たず、一度限りのインスタンス生成用に使われます。主にリスナー登録などの簡易実装に利用され、クラスファイル名には $1, $2 などが自動付与されます。

匿名内部クラスの応用例

イベントハンドラなどの実装時、専用のクラスファイルを作らずに済みます。

public interface EventHandler {
    void onTrigger();
}

// 簡易的な実装
EventHandler handler = new EventHandler() {
    @Override
    public void onTrigger() {
        System.out.println("アクションが発生しました");
    }
};
handler.onTrigger();

バイトコードの確認方法

IDE(Eclipse など)では、ビルド後の .class ファイルを参照することで、コンパイル結果を確認できます。ナビゲータビューにおいて、プロジェクト内の bin ディレクトリを展開すると、ソースコードに対応するバイナリファイルを閲覧することが可能です。特に内部クラスの場合は、Outer$Inner.class のような命名規則で生成されていることが確認できます。

タグ: Java abstract-class interface Polymorphism inner-class

7月14日 00:54 投稿