継承

既存クラスの機能を拡張する仕組み

継承の基本概念

継承とは、既存のクラス(スーパークラス/親クラス)からその特性と振る舞いを引き継いで、新しいクラス(サブクラス/子クラス)を作成する仕組みです。これにより、コードの再利用性が高まり、クラス間の階層関係を表現できます。

クラス図

図1: 継承のクラス図

基本的な継承の例

// スーパークラス(親クラス)
public class Vehicle {
    protected String brand;
    protected String model;
    protected int year;
    
    public Vehicle(String brand, String model, int year) {
        this.brand = brand;
        this.model = model;
        this.year = year;
    }
    
    public void startEngine() {
        System.out.println("エンジンを始動します");
    }
    
    public void stopEngine() {
        System.out.println("エンジンを停止します");
    }
    
    public String getInfo() {
        return year + "年製 " + brand + " " + model;
    }
}

// サブクラス(子クラス)
public class Car extends Vehicle {
    private int numDoors;
    private String bodyType;
    
    public Car(String brand, String model, int year, int numDoors, String bodyType) {
        super(brand, model, year);  // 親クラスのコンストラクタを呼び出し
        this.numDoors = numDoors;
        this.bodyType = bodyType;
    }
    
    // メソッドのオーバーライド
    @Override
    public void startEngine() {
        System.out.println("プッシュボタンでエンジンを始動します");
    }
    
    // 新しいメソッドの追加
    public void honkHorn() {
        System.out.println("ビープ! ビープ!");
    }
    
    @Override
    public String getInfo() {
        return super.getInfo() + ", " + numDoors + "ドア, " + bodyType;
    }
}

シーケンス図

図2: 継承の動作シーケンス

super キーワード

super キーワードは、親クラスのメンバ(フィールドとメソッド)にアクセスするために使用します:

  • super() - 親クラスのコンストラクタを呼び出す
  • super.methodName() - 親クラスのメソッドを呼び出す
  • super.fieldName - 親クラスのフィールドにアクセスする

メソッドのオーバーライド

サブクラスで親クラスのメソッドを再定義することをオーバーライドと言います。オーバーライドの基本ルール:

  • メソッド名、パラメータリスト、戻り値の型が同じでなければならない
  • アクセス修飾子は、親クラスのメソッドより厳しくできない(例:親がprotectedなら、子はprivateにできない)
  • 親メソッドがthrowsする例外より多くの例外をthrowsできない
  • @Override アノテーションを付けることが推奨される(コンパイラによる検証が行われる)

final キーワード

継承に関連するfinalキーワードの使用法:

  • final class - このクラスを継承できなくする
  • final method - このメソッドをサブクラスでオーバーライドできなくする
  • final variable - この変数の値を変更できなくする(定数)

finalの使用例

public final class ImmutableClass {
    private final int value;
    
    public ImmutableClass(int value) {
        this.value = value;
    }
    
    public final int getValue() {
        return value;
    }
}

// 以下のクラス定義はコンパイルエラー
// public class SubClass extends ImmutableClass {}

抽象クラス

抽象クラスは、直接インスタンス化できないクラスで、サブクラスによる実装を前提としています。abstract キーワードを使用して定義します。

抽象クラスの例

public abstract class Shape {
    protected String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    // 抽象メソッド(実装は提供せず、サブクラスでの実装を強制)
    public abstract double calculateArea();
    
    // 具象メソッド(実装を提供)
    public String getColor() {
        return color;
    }
}

public class Circle extends Shape {
    private double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    // 抽象メソッドの実装を提供
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

継承の利点と注意点

利点:

  • コードの再利用性が向上する
  • クラス階層によって関連を明確に表現できる
  • 共通機能を親クラスに集約でき、保守性が向上する

注意点:

  • 過度な継承に注意: 深い継承階層は理解しづらく保守が難しくなる
  • 脆弱な基底クラス問題: 親クラスの変更がサブクラスに予期せぬ影響を与えることがある
  • 継承より合成を優先する原則: 継承よりも、インターフェースと合成(Composition)の使用を検討する
  • LSP(リスコフの置換原則): サブクラスはスーパークラスの代わりに使えるべき

Java の継承の制限

Javaはクラスの多重継承をサポートしていません(一つのクラスは一つの親クラスのみ継承可能)。多重継承に起因する「ダイヤモンド問題」を避けるためです。代わりに、インターフェースを使用して複数の振る舞いを組み合わせることができます。