Javaにおける継承の手引き

Javaの継承ガイド

1. 概要

オブジェクト指向プログラミングのコア原則の1つ–inheritance – enables us to reuse existing code or extend an existing type.

簡単に言えば、Javaでは、クラスは別のクラスと複数のインターフェイスを継承できますが、インターフェイスは他のインターフェイスを継承できます。

この記事では、継承の必要性から始めて、継承がクラスとインターフェースでどのように機能するかについて説明します。

次に、変数/メソッド名とアクセス修飾子が継承されるメンバーにどのように影響するかについて説明します。

最後に、型を継承することの意味を説明します。

2. 継承の必要性

自動車メーカーとして、複数の車種を顧客に提供するとします。 車のモデルが異なるとサンルーフや防弾窓などの機能が異なる場合でも、それらにはすべてエンジンやホイールなどの共通のコンポーネントと機能が含まれます。

各車のモデルを個別にゼロから設計するのではなく、基本設計を作成し、それを拡張して特殊なバージョンを作成することは理にかなっています。

同様の方法で、継承を使用して、基本機能と動作を備えたクラスを作成し、この基本クラスを継承するクラスを作成することにより、その特殊バージョンを作成できます。 同様に、インターフェイスは既存のインターフェイスを拡張できます。

別のタイプに継承されるタイプを指すために複数の用語が使用されていることに気付くでしょう。具体的には、次のとおりです。

  • 基本型は、スーパー型または親型とも呼ばれます

  • 派生型は、拡張型、サブ型、または子型と呼ばれます

3. クラスとの継承

3.1. クラスを拡張する

クラスは別のクラスを継承し、追加のメンバーを定義できます。

基本クラスCarを定義することから始めましょう:

public class Car {
    int wheels;
    String model;
    void start() {
        // Check essential parts
    }
}

クラスArmoredCarは、using the keyword extends in its declarationによってCarクラスのメンバーを継承できます。

public class ArmoredCar extends Car {
    int bulletProofWindows;
    void remoteStartCar() {
    // this vehicle can be started by using a remote control
    }
}

Classes in Java support single inheritance; ArmoredCarクラスは複数のクラスを拡張できません。 extendsキーワードがない場合、クラスは暗黙的にクラスjava.lang.Objectを継承します。

3.2. 継承されるもの

基本的に、派生クラスは、static.ではない基本クラスからprotectedおよびpublicメンバーを継承します。さらに、defaultおよびpackageアクセスを持つメンバー2つのクラスが同じパッケージにある場合は継承されます。

基本クラスでは、派生クラスがすべてのコードにアクセスできるわけではありません。

クラスのprivateおよびstaticメンバーは、派生クラスに継承されません。 また、基本クラスと派生クラスが別々のパッケージで定義されている場合、基本クラスでdefaultまたはpackageアクセス権を持つメンバーは派生クラスに継承されません。

3.3. 派生クラスから親クラスメンバーにアクセスする

簡単だ。 それらを使用するだけです(メンバーにアクセスするために基本クラスへの参照は必要ありません)。 以下に簡単な例を示します。

public class ArmoredCar extends Car {
    public String registerModel() {
        return model;
    }
}

3.4. 非表示の基本クラスインスタンスメンバー

基本クラスと派生クラスの両方が同じ名前の変数またはメソッドを定義するとどうなりますか? 心配しないでください。まだ両方にアクセスできます。 ただし、変数またはメソッドの前にキーワードthisまたはsuperを付けることにより、Javaに対して意図を明確にする必要があります。

thisキーワードは、それが使用されているインスタンスを参照します。 superキーワードは(明らかなように)親クラスインスタンスを参照します。

public class ArmoredCar extends Car {
    private String model;
    public String getAValue() {
        return super.model;   // returns value of model defined in base class Car
        // return this.model;   // will return value of model defined in ArmoredCar
        // return model;   // will return value of model defined in ArmoredCar
    }
}

多くの開発者は、thisおよびsuperキーワードを使用して、参照している変数またはメソッドを明示的に示しています。 ただし、すべてのメンバーでそれらを使用すると、コードが乱雑に見える可能性があります。

3.5. 非表示の基本クラス静的メンバー

基本クラスと派生クラスが同じ名前の静的変数とメソッドを定義するとどうなりますか? インスタンス変数の場合と同じように、派生クラスの基本クラスからstaticメンバーにアクセスできますか?

例を使用して調べてみましょう。

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return super.msg(); // this won't compile.
    }
}

いいえ、できません。 静的メンバーはインスタンスではなくクラスに属します。 したがって、msg()で非静的superキーワードを使用することはできません。

静的メンバーはクラスに属しているため、上記の呼び出しを次のように変更できます。

return Car.msg();

基本クラスと派生クラスの両方が同じ署名を持つ静的メソッドmsg()を定義する次の例を考えてみます。

public class Car {
    public static String msg() {
        return "Car";
    }
}
public class ArmoredCar extends Car {
    public static String msg() {
        return "ArmoredCar";
    }
}

それらを呼び出す方法は次のとおりです。

Car first = new ArmoredCar();
ArmoredCar second = new ArmoredCar();

上記のコードの場合、first.msg()は「Car」を出力し、second.msg()は「ArmoredCar」を出力します。 呼び出される静的メッセージは、ArmoredCarインスタンスを参照するために使用される変数のタイプによって異なります。

4. インターフェイスによる継承

4.1. 複数のインターフェースの実装

クラスは1つのクラスのみを継承できますが、複数のインターフェイスを実装できます。

前のセクションで定義したArmoredCarがスーパースパイに必要であると想像してください。 したがって、Carの製造会社は、飛行機能と浮動機能を追加することを考えました。

public interface Floatable {
    void floatOnWater();
}
public interface Flyable {
    void fly();
}
public class ArmoredCar extends Car implements Floatable, Flyable{
    public void floatOnWater() {
        System.out.println("I can float!");
    }

    public void fly() {
        System.out.println("I can fly!");
    }
}

上記の例では、インターフェイスから継承するためにキーワードimplementsが使用されていることがわかります。

4.2. 多重継承の問題

Javaでは、インターフェースを使用した多重継承が許可されています。

Java 7までは、これは問題ではありませんでした。 インターフェイスはabstractメソッド、つまり実装のないメソッドしか定義できませんでした。 したがって、クラスが同じメソッドシグネチャを持つ複数のインターフェイスを実装していれば、問題はありませんでした。 実装クラスには、実装するメソッドが1つだけありました。

Java 8のインターフェイスにdefaultメソッドを導入することで、この単純な方程式がどのように変化したかを見てみましょう。

Starting with Java 8, interfaces could choose to define default implementations for its methods(インターフェイスは引き続きabstractメソッドを定義できます)。 これは、クラスが同じシグネチャを持つメソッドを定義する複数のインターフェースを実装する場合、子クラスは個別の実装を継承することを意味します。 これは複雑に聞こえますが、許可されていません。

Javaは、別々のインターフェースで定義された同じメソッドの複数の実装の継承を許可しません。

例を示しましょう。

public interface Floatable {
    default void repair() {
        System.out.println("Repairing Floatable object");
    }
}
public interface Flyable {
    default void repair() {
        System.out.println("Repairing Flyable object");
    }
}
public class ArmoredCar extends Car implements Floatable, Flyable {
    // this won't compile
}

両方のインターフェースを実装する場合は、repair()メソッドをオーバーライドする必要があります。

上記の例のインターフェースが同じ名前、たとえばdurationの変数を定義している場合、変数名の前にインターフェース名を付けないと、それらにアクセスできません。

public interface Floatable {
    int duration = 10;
}
public interface Flyable {
    int duration = 20;
}
public class ArmoredCar extends Car implements Floatable, Flyable {

    public void aMethod() {
        System.out.println(duration); // won't compile
        System.out.println(Floatable.duration); // outputs 10
        System.out.println(Flyable.duration); // outputs 20
    }
}

4.3. 他のインターフェースを拡張するインターフェース

インターフェースは複数のインターフェースを拡張できます。 例を示しましょう。

public interface Floatable {
    void floatOnWater();
}
interface interface Flyable {
    void fly();
}
public interface SpaceTraveller extends Floatable, Flyable {
    void remoteControl();
}

インターフェイスは、キーワードextendsを使用して他のインターフェイスを継承します。 クラスはキーワードimplementsを使用してインターフェースを継承します。

5. 継承タイプ

クラスが別のクラスまたはインターフェイスを継承する場合、そのメンバーを継承するだけでなく、そのクラスも継承します。 これは、他のインターフェイスを継承するインターフェイスにも適用されます。

これは非常に強力な概念であり、開発者は実装(コンクリートクラスまたは派生クラス)にプログラミングするのではなく、インターフェイス(ベースクラスまたはインターフェイス)にプログラミングできます。

たとえば、従業員が所有する車のリストを組織が管理している状況を想像してください。 もちろん、すべての従業員が異なる車種を所有している場合があります。 それでは、異なる車のインスタンスをどのように参照できますか? これが解決策です:

public class Employee {
    private String name;
    private Car car;

    // standard constructor
}

Carのすべての派生クラスはタイプCarを継承するため、派生クラスインスタンスはクラスCarの変数を使用して参照できます。

Employee e1 = new Employee("Shreya", new ArmoredCar());
Employee e2 = new Employee("Paul", new SpaceCar());
Employee e3 = new Employee("Pavni", new BMW());

6. 結論

この記事では、Java言語の核となる側面、つまり継承の仕組みについて説明しました。

Javaがクラスを使用して単一の継承をサポートし、インターフェースを使用して複数の継承をサポートする方法について説明し、言語でのメカニズムの動作の複雑さについて説明しました。

いつものように、例の完全なソースコードはover on GitHubで入手できます。