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
    }
}

これで、 ArmoredCarクラスはCar、のサブクラスであり、後者はArmoredCarのスーパークラスであると言えます。

Javaのクラスは単一継承をサポートします。 ArmoredCarクラスは複数のクラスを拡張できません。

また、 extends キーワードがない場合、クラスは暗黙的にクラスjava.lang.Objectを継承することに注意してください。

サブクラスクラスは、非静的保護メンバーとパブリックメンバーをスーパークラスクラスから継承します。さらに、デフォルト package-private)アクセス権を持つメンバーが継承されます2つのクラスが同じパッケージに含まれている場合。

一方、クラスのprivateおよびstaticメンバーは継承されません。

3.2. 子クラスからの親メンバーへのアクセス

継承されたプロパティまたはメソッドにアクセスするには、それらを直接使用できます。

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

そのメンバーにアクセスするためにスーパークラスへの参照は必要ないことに注意してください。

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つだけありました。

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

Java 8以降、インターフェースはそのメソッドのデフォルト実装を定義することを選択できます(インターフェースは引き続き 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. 隠されたクラスのメンバー

6.1. 非表示のインスタンスメンバー

スーパークラスとサブクラスの両方が同じ名前の変数またはメソッドを定義するとどうなりますか? 心配しないで; まだ両方にアクセスできます。 ただし、変数またはメソッドの前にキーワード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キーワードを使用して、参照している変数またはメソッドを明示的に示します。 ただし、すべてのメンバーでそれらを使用すると、コードが乱雑に見える可能性があります。

6.2. 隠された静的メンバー

基本クラスとサブクラスが同じ名前の静的変数とメソッドを定義するとどうなりますか? インスタンス変数の場合と同じように、派生クラスの基本クラスから 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インスタンスを参照するために使用される変数のタイプによって異なります。

7. 結論

この記事では、Java言語のコアな側面である継承について説明しました。

Javaがクラスによる単一継承とインターフェースによる多重継承をどのようにサポートするかを見て、メカニズムが言語でどのように機能するかについての複雑さについて説明しました。

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