Javaにおける継承の手引き

1概要

オブジェクト指向プログラミングのコア原則の1つである** 継承 - によって、既存のコードを再利用したり、既存の型を拡張したりすることができます。

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

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

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

そして最後に、型を継承することの意味がわかります。

2継承の必要性

自動車メーカーとして、あなたがあなたの顧客に複数の自動車モデルを提供すると想像してください。車種が異なれば、サンルーフや防弾窓などのさまざまな機能が提供される可能性がありますが、それらにはすべてエンジンやホイールなどの共通のコンポーネントや機能が含まれています。

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

同様に、継承を使って、この基本クラスを継承するクラスを作成することによって、基本的な機能と動作を持つクラスを作成し、その特殊なバージョンを作成できます。同様に、インタフェースは既存のインタフェースを拡張できます。

別の型によって継承される型を指すために複数の用語が使用されていることに注意してください。具体的には、

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

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

3クラスとの継承

3.1. クラスを拡張する

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

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

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

クラス ArmoredCar は、その宣言内でキーワード extends を使用して** によって Car クラスのメンバーを継承できます。

public class ArmoredCar extends Car {
    int bulletProofWindows;
    void remoteStartCar() {
   //this vehicle can be started by using a remote control
    }
}
  • Javaのクラスは単一継承をサポートしています** 。 ArmoredCar クラスは複数のクラスを拡張できません。 extends キーワードがない場合、クラスは暗黙的にclass 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 メソッドの導入によってどのように変化したかを見てみましょう。

  • Java 8以降、インタフェースはそのメソッドのデフォルト実装を定義することを選択することができました(インタフェースは__抽象メソッドを定義することもできます)。つまり、クラスが同じシグネチャを持つメソッドを定義する複数のインタフェースを実装する場合、子クラスは別々の実装を継承します。これは複雑に聞こえ、許可されていません。

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がクラスを使った単一継承とインターフェースを使った多重継承をどのようにサポートしているかを述べ、そのメカニズムが言語の中でどのように機能するかの複雑さについて議論しました。

いつものように、例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-lang-oop[GitHubで利用可能]です。