継承の基本概念
継承とは、既存のクラス(スーパークラス/親クラス)からその特性と振る舞いを引き継いで、新しいクラス(サブクラス/子クラス)を作成する仕組みです。これにより、コードの再利用性が高まり、クラス間の階層関係を表現できます。
クラス図
図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はクラスの多重継承をサポートしていません(一つのクラスは一つの親クラスのみ継承可能)。多重継承に起因する「ダイヤモンド問題」を避けるためです。代わりに、インターフェースを使用して複数の振る舞いを組み合わせることができます。