JAVAの継承の基本
Javaの継承は、オブジェクト指向プログラミングの三大要素(カプセル化、継承、多様性)の1つであり、既存のクラスのプロパティとメソッドを新しいクラスが受け継ぐ仕組みです。
これにより、コードの再利用性を高め、開発効率を向上させ、プログラムの構造をより明確にすることができます。
1. 継承の基本概念とメリット
1.1. 継承とは
継承では、プロパティやメソッドを提供する側のクラスをスーパークラスと呼び、それらを受け継ぐ側のクラスをサブクラスと呼びます。
Javaでは、extendsキーワードを使用して継承を定義します
基本構文:
class SubClass extends SuperClass {
// サブクラス独自のフィールドとメソッド
}
1.2. 継承のメリット
継承には主に以下のメリットがあります。
コードの再利用性: スーパークラスに実装されたコードをサブクラスでそのまま利用できるため、同じコードを何度も書く必要がなくなります。
保守性の向上: 共通の機能がスーパークラスに一元化されるため、機能の変更が必要な場合に、スーパークラスだけを修正すれば、すべてのサブクラスに反映されます。
多態性の実現: 継承は多態性の基盤となります。スーパークラス型の変数にサブクラスのインスタンスを代入することで、共通のインターフェースを通じて異なるオブジェクトを扱うことが可能になります。
構造の明確化: クラス間に「is-a」の関係(「〜は〜の一種です」)を明確に定義でき、プログラムの構造が論理的に整理されます。
2. 継承の基本的な実装
動物(Animal)をスーパークラスとし、犬(Dog)をサブクラスとする継承の例です。
2.1. スーパークラスの定義
// スーパークラス
class Food {
String name;
// コンストラクタ
public Food(String name) {
this.name = name;
System.out.println("Foodコンストラクタが呼び出されました。名前: " + name);
}
// 共通のメソッド
public void eat() {
System.out.println(name + "は食べ物です。");
}
public void sleep() {
System.out.println(name + "は眠っています。");
}
}
2.2. サブクラスの定義とextendsキーワード
DogクラスはAnimalクラスを継承します。
// サブクラス
class Dog extends Animal {
String breed; // 犬独自のフィールド
// サブクラスのコンストラクタ
// スーパークラスのコンストラクタを明示的に呼び出す必要がある
public Dog(String name, String breed) {
// super()でスーパークラスのコンストラクタを呼び出す
super(name);
this.breed = breed;
System.out.println("Dogコンストラクタが呼び出されました。犬種: " + breed);
}
// 犬独自のメソッド
public void bark() {
System.out.println(name + "はワンワン吠えています。");
}
// スーパークラスから継承したメソッドを利用
public void displayInfo() {
System.out.println("名前: " + name + ", 犬種: " + breed);
eat(); // 継承したメソッドの呼び出し
}
}
2.3. 実行例
public class InheritanceExample {
public static void main(String[] args) {
// Dogクラスのインスタンスを作成
Dog myDog = new Dog("ポチ", "柴犬");
System.out.println("----- Dogクラスのメソッド -----");
myDog.bark(); // Dog独自のメソッド
myDog.displayInfo(); // Dog独自のメソッド
System.out.println("----- Animalクラスから継承したメソッド -----");
myDog.eat(); // Animalから継承
myDog.sleep(); // Animalから継承
// 継承したフィールドにアクセス(ただし、フィールドはprivateにすべき)
System.out.println("犬の名前: " + myDog.name);
}
}
実行結果:
Animalコンストラクタが呼び出されました。名前: ポチ
Dogコンストラクタが呼び出されました。犬種: 柴犬
----- Dogクラスのメソッド -----
ポチはワンワン吠えています。
名前: ポチ, 犬種: 柴犬
ポチは食べ物を食べています。
----- Animalクラスから継承したメソッド -----
ポチは食べ物を食べています。
ポチは眠っています。
犬の名前: ポチ
結果よりDogクラスがAnimalクラスのフィールド(name)とメソッド(eat(), sleep())を
自動的に受け継いでいることがわかります。
3. 継承とアクセス修飾子
継承において、アクセス修飾子(public, protected, デフォルト(パッケージプライベート), private)は、スーパークラスのメンバー(フィールドやメソッド)がサブクラスからどこまでアクセス可能かを決定する上で重要です。
・private: スーパークラス内からのみアクセス可能で、サブクラスを含め外部からは一切アクセスできません。
・protected: 同一パッケージ内、および異なるパッケージのサブクラスからアクセス可能です。継承を使用する場合、サブクラスからアクセスさせたい共通のメンバーにはこの修飾子を使うことが一般的です。
・public: どこからでもアクセス可能です。
3.1. privateメンバーと継承
スーパークラスのprivateメンバーはサブクラスに継承されますが、直接アクセスすることはできません。アクセスするには、スーパークラスで定義されたpublicやprotectedのアクセサメソッド(Getter/Setter)を経由する必要があります。
class SecretAgent {
private String secretName = "James Bond"; // private
public String getSecretName() { // publicなGetter
return secretName;
}
}
class NewAgent extends SecretAgent {
public void checkSecret() {
// System.out.println(secretName); // コンパイルエラー: privateメンバーへの直接アクセス
System.out.println("秘密の名前をGetter経由で確認: " + getSecretName()); // OK
}
}
4. superキーワード
superキーワードは、サブクラスからスーパークラスのメンバーにアクセスするために使われます。主に以下の2つの用途があります。
4.1. スーパークラスのコンストラクタ呼び出し
・サブクラスのコンストラクタの最初の行で、super(...)を使用してスーパークラスのコンストラクタを明示的に呼び出す必要があります。
・スーパークラスに引数なしのデフォルトコンストラクタがある場合、super()を記述しなくても、コンパイラが自動的に挿入します。
・スーパークラスに引数付きのコンストラクタしかない(引数なしのコンストラクタが定義されていない)場合は、サブクラスで必ず明示的にsuper(...)を呼び出す必要があります。
class Vehicle {
public Vehicle(String type) {
System.out.println("Vehicleが生成されました。タイプ: " + type);
}
}
class Car extends Vehicle {
public Car(String brand) {
// 必須:スーパークラスのコンストラクタを呼び出す
super("乗用車");
System.out.println("Carが生成されました。ブランド: " + brand);
}
}
4.2. スーパークラスのメソッドまたはフィールドへのアクセス
サブクラスでスーパークラスと同じ名前のメソッドやフィールドが定義されている場合、super.methodName()やsuper.fieldNameを使って、隠蔽されたスーパークラス側のメンバーにアクセスできます。
5. メソッドのオーバーライド
メソッドのオーバーライドとは、サブクラスがスーパークラスから継承したメソッドと同じシグネチャ(メソッド名、引数の型と数)を持つメソッドを再定義することです。これにより、サブクラスはスーパークラスの振る舞いを上書きし、独自の振る舞いを実装できます。
5.1. オーバーライドのルール
1.シグネチャの一致: メソッド名、引数の型と数はスーパークラスと完全に一致しなければなりません。
2.戻り値の型: スーパークラスの戻り値の型と同じか、共変な戻り値の型である必要があります(Java 5.0以降)。共変とは、スーパークラスの戻り値の型のサブタイプ(子クラス)であることです。
3.アクセス修飾子: サブクラスのアクセス修飾子は、スーパークラスのアクセス修飾子よりも緩く設定することはできますが、厳しく設定することはできません(例: protectedをpublicにはできるが、publicをprivateにはできない)。
4.例外: スーパークラスのメソッドがスローする検査例外(Checked Exception)より広範囲な例外をスローすることはできません。
5.Overrideアノテーション: 必須ではありませんが、オーバーライドしていることを明示し、コンパイラにチェックさせるために強く推奨されます。
5.2. オーバーライドの例
class Shape {
public double getArea() {
System.out.println("図形の面積を計算します。");
return 0.0;
}
}
class Circle extends Shape {
double radius;
public Circle(double r) {
this.radius = r;
}
// メソッドのオーバーライド
@Override
public double getArea() {
System.out.println("円の面積を計算しています。");
return Math.PI * radius * radius;
}
}
public class OverrideExample {
public static void main(String[] args) {
Shape genericShape = new Shape();
Circle myCircle = new Circle(5.0);
System.out.println("Shapeの面積: " + genericShape.getArea());
System.out.println("Circleの面積: " + myCircle.getArea());
}
}
実行結果:
図形の面積を計算します。
Shapeの面積: 0.0
円の面積を計算しています。
Circleの面積: 78.53981633974483
CircleクラスはShapeクラスのgetArea()メソッドをオーバーライドし、独自の円の面積計算を実装しています。
6. 継承と多態性
継承の真価は、多態性と組み合わされたときに発揮されます。多態性とは、「一つのインターフェースが複数の形態(クラス)を持つ」ことを意味します。
6.1. アップキャスティング
スーパークラス型の変数にサブクラスのインスタンスを代入することをアップキャスティングと呼びます。
// Shape (スーパークラス)型の変数に Circle (サブクラス)のインスタンスを代入
Shape s1 = new Circle(10); // アップキャスティング
アップキャスティングされたオブジェクトに対してメソッドを呼び出すと、実行時にオブジェクトの実際の型(この場合はCircle)で定義されているオーバーライドされたメソッドが実行されます。これを動的ディスパッチと呼びます。
7. 継承の制限と特殊なクラス
7.1. Javaは単一継承のみ
Javaでは、一つのクラスが直接継承できるスーパークラスは1つだけです。これを単一継承と呼びます。
// public class Child extends ParentA, ParentB {} // コンパイルエラー: 多重継承は不可
多重継承が原因で発生する「ダイヤモンド問題」などの複雑性を避けるためです。複数のクラスから機能を継承したい場合は、インターフェースを使用します。Javaではクラスの継承は単一ですが、インターフェースの実装は複数(implements InterfaceA, InterfaceB)可能です。
7.2. finalキーワードによる制限
1.final class: クラス宣言にfinalを付けると、そのクラスは継承されることができなくなります(サブクラスを作れない)。
final class CannotBeExtended { /* ... */ }
// class Child extends CannotBeExtended {} // コンパイルエラー
2.final method: メソッドにfinalを付けると、そのメソッドはオーバーライドされることができなくなります。
class Parent {
public final void doNotOverride() { /* ... */ }
}
// class Child extends Parent { @Override public void doNotOverride() {} } // コンパイルエラー
7.3. 抽象クラス
抽象クラスは、インスタンス化できない(オブジェクトを直接作れない)クラスです。クラス宣言にabstractキーワードを付けます。
・抽象クラスは、一つ以上の抽象メソッド(abstractキーワード付きで、実装を持たないメソッド)を持つことができます。
・抽象クラスを継承したサブクラスは、スーパークラスのすべての抽象メソッドを実装しなければなりません(または、自身も抽象クラスである必要があります)。
用途: 共通の基盤機能を提供しつつ、具体的な実装はサブクラスに強制したい場合に利用されます。
例:
abstract class LivingThing {
String name;
public LivingThing(String name) { this.name = name; }
// 抽象メソッド(実装はサブクラスに任せる)
public abstract void move();
// 通常のメソッドも持てる
public void breath() {
System.out.println(name + "は呼吸をしています。");
}
}
class Fish extends LivingThing {
public Fish(String name) { super(name); }
// 抽象メソッドの実装は必須
@Override
public void move() {
System.out.println(name + "は泳いでいます。");
}
}
public class AbstractExample {
public static void main(String[] args) {
// LivingThing lt = new LivingThing("抽象"); // コンパイルエラー: 抽象クラスはインスタンス化できない
Fish myFish = new Fish("ニモ");
myFish.breath();
myFish.move();
}
}