Javaの継承についてまとめました


# はじめに
現在、私が進行しているJavaの勉強会でまとめた内容です。
内容に不正確な点があれば、ご指摘いただけるとありがたいです。

- 韓国人として、日本語とコンピュータの勉強を同時に行うために、ここに文章を書いています
- 翻訳ツールの助けを借りて書いた文章なので、誤りがあるかもしれません

https://github.com/whiteship/live-study/issues/6

# 目次
- Java 継承の特徴
- super キーワード
- メソッドオーバーライド
- ダイナミックメソッドディスパッチ (Dynamic Method Dispatch)
- 抽象クラス
- final キーワード
- Object クラス

# Javaの継承の特徴
## 継承とは？
言葉の意味そのままに親クラスの変数とメソッドを子クラスが受け継ぐことを言います。
### 継承を使用する理由
>The idea of inheritance is simple but powerful: When you want to create a new class and there is already a class that includes some of the code that you want, you can derive your new class from the existing class. In doing this, you can reuse the fields and methods of the existing class without having to write (and debug!) them yourself.

継承の概念は簡単ですが強力です。新しいクラスを作成しようとする際に、すでに必要なコードの一部を含むクラスがあれば、そのクラスから新しいクラスを派生させることができます。これにより、既存のクラスのフィールドとメソッドを自分で記述（そしてデバッグ！）することなく再利用できます。
まとめると以下のようになります。

コードの再利用性を通じてコードの簡潔化を確保するため。

### 継承の方法

- `extends`
    - `extends` は宣言されたクラスの直接スーパークラス（direct superclass）を指定します
```java
{アクセス修飾子} class A extends ClassType
```

:::note info
**直接スーパークラス (Direct Superclass)**

- 直接継承されたクラス  
- クラス宣言の `extends` で指定されたクラスが該当します

**推移的閉包 (Transitive Closure)**

- 継承の継承、つまり親クラスの親クラスもスーパークラスとみなします

```java
class A { }          // A は最上位のスーパークラス  
class B extends A { } // B は A の直接サブクラス、A は B の直接スーパークラス  
class C extends B { } // C は B の直接サブクラス、B は C の直接スーパークラス  
```
A は C のスーパークラス（間接的な継承関係）
:::

- `Object` クラスの定義では **`extends`** を使用することはできません
    - これに違反するとコンパイルエラーが発生します
    - なぜなら、`Object` は基本クラス（primordial class）であり、直接スーパークラスを持たないからです

- `ClassType` はアクセス可能なクラスでなければなりません

### クラス継承のアクセス修飾子（演算子）

- `sealed` クラスを継承する場合、許可されたサブクラス（`permitted subclass`）として明示されている必要があります
- `sealed` クラスは必ず以下のキーワードのいずれかで宣言されなければなりません：
    - `sealed`: 他の制限付き継承を許可
    - `non-sealed`: 制限なく継承を許可
    - `final`: これ以上の継承を禁止


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/f59ee5da-17ac-c215-519d-2a0b93f7896e.png)

- `non-sealed` は継承を許可します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/37b3e108-f59d-4b3b-e49e-0c2fb07642fa.png)

- `final` クラスは継承できません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/71e4a039-0de4-f3f7-c0ef-8ae69d700195.png)


### さまざまなクラスの種類

#### ENUM
- `enum` クラスは列挙型のみ継承可能です。  
    - `enum` は `final` クラスであるため、他のクラスから拡張することはできません。  
    - `java.lang.Enum` クラスを継承しています
    
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/851d79c7-d511-a209-1904-e14e28f7c216.png)

`enum` に関しては次回、別途取り上げることにします

#### record

- `record` も継承できません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/49bc4270-40c5-71ce-0b03-03c0b236b702.png)


### Java の継承の使い方と注意点

- 多重継承は禁止
    - 複数のクラスから同じフィールドを継承する場合に発生する可能性がある曖昧性や複雑性を防ぐため

:::note info
ダイヤモンド問題（Diamond Problem）

```java
class A {
    void greet() {
        System.out.println("Hello from A");
    }
}

class B extends A {
    @Override
    void greet() {
        System.out.println("Hello from B");
    }
}

class C extends A {
    @Override
    void greet() {
        System.out.println("Hello from C");
    }
}

// 多重継承を仮定: D が B と C を同時に継承
class D extends B, C {
    // D は greet() メソッドを使用したい
}
```

このような場合、ランタイムで D を作成する際に、B と A を生成すべきか、C と A を生成すべきかが曖昧になります。

これを「ダイヤモンド問題」と呼びます。
:::

 ダイヤモンド問題（多重継承禁止）に関する公式チュートリアル

https://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html


## SUPER


- `super` というキーワードを使用して、サブクラスからスーパークラスにアクセスできます
- `super` は参照変数です
- 子クラスのコンストラクタでは、必ず最初の行に配置する必要があります

```java
class Outer {
    int secret = 5;
    class Inner {
        int getSecret() { return secret; }
        void setSecret(int s) { secret = s; }
    }
}

class ChildOfInner extends Outer.Inner {
    ChildOfInner(Outer x) {
        x.super(); // Outer インスタンスを渡す
    }
}

public class Test {
    public static void main(String[] args) {
        Outer x = new Outer();
        ChildOfInner a = new ChildOfInner(x);
        ChildOfInner b = new ChildOfInner(x);

        System.out.println(b.getSecret()); // 出力: 5
        a.setSecret(6);
        System.out.println(b.getSecret()); // 出力: 6
    }
}
```
https://docs.oracle.com/javase/specs/jls/se23/html/jls-8.html#jls-8.8.7.1


## メソッドオーバーライド

- スーパークラスのメソッドをサブクラスが新しいロジックで定義したい場合に使用します
    - 再定義が目的であるため、定義しなくても問題ありません

- `@Override` は省略可能です
    - `@Override` を明示すると、コンパイラがそのメソッドが親クラスまたはインターフェースのメソッドを正確にオーバーライドしているかを検査します。（開発者のミスを減らす）
    - 可読性向上（チーム作業や意図の把握が容易になります）

- 親クラスのメソッドのアクセス修飾子を狭めることはできません


| 親クラスのメソッド | 子クラスのメソッド            |
|-------------------|--------------------------------|
| public            | public                         |
| protected         | protected, public              |
| default（パッケージ専用） | default, protected, public      |


```java
class Parent {
    void greet() {} // default
}

class Child extends Parent {
    @Override
    private void greet() {} // コンパイルエラー: アクセス範囲を狭めることはできません
}
```



:::note warn
`@Override` を使用すべき理由
```java
class Animal{
    public void sayHello(){}

}

class Dog extends Animal{
    @Override
    public void sayHello(){
        System.out.println("wow bowl");
    }
}  
```

通常、上記のコードのようにメソッドを定義することができます。

しかし、`@Override` を削除すると、どのような問題が発生するのでしょうか？

```java
class Animal{
    public void sayHello(){}

}

class Dog extends Animal {
    public void sayHello1(){
        System.out.println("wow bowl");
    }
} 
```
次のように間違ったメソッドを定義すると、このメソッドは `sayHello1` となり、`Animal` の `sayHello` にはなりません。

そのため、親クラスのメソッドを再定義していることをコンパイラに伝えるために `@Override` を使用します。これにより、コンパイル時に検証することができます。

また、他の開発者がコードを見たときに、このロジックが親クラスのメソッドを再定義したものだと一目で理解できます。

以上の理由から、`@Override` を使用します。
:::

## ダイナミックディスパッチ(Dynamic Dispatch)とは？

- 実行時にメソッド呼び出しを決定する仕組み
- ポリモーフィズムの核心概念であり、親クラスの参照変数が子クラスのオブジェクトを参照する場合、呼び出されるメソッドは参照変数の型ではなく、実際のオブジェクトの型に基づいて決まります。

### **スタティックディスパッチ(Static Dispatch)との違い**

- **スタティックディスパッチ**: コンパイル時に呼び出すメソッドが決定される。
    - メソッドオーバーロード(Method Overloading)で使用される。
- **ダイナミックディスパッチ**: 実行時に呼び出すメソッドが決定される。
    - メソッドオーバーライド(Method Overriding)で使用される。

| 特徴                | オーバーロード (Overloading) | オーバーライド (Overriding) |
|---------------------|------------------------------|-----------------------------|
| **決定時点**         | コンパイル時                 | 実行時                      |
| **ダイナミックディスパッチ適用** | 適用されない                  | 適用される                  |
| **基準**             | メソッドの引数シグネチャ       | オブジェクトの実際の型        |


```java
class Parent {
    void greet() {
        System.out.println("Hello from Parent");
    }
}

class Child extends Parent {
    @Override
    void greet() {
        System.out.println("Hello from Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent child = new Child(); // 親クラス型の参照変数で子クラスのオブジェクトを参照

        parent.greet(); // 出力: Hello from Parent
        child.greet();  // 出力: Hello from Child (ダイナミックディスパッチ)
    }
}
```

- 次のように、ランタイムでどの実装クラスを使用するかが判断されます

# ダブルディスパッチ (Double Dispatch)

- Java は基本的にシングルディスパッチのみをサポートします
- しかし、メソッド呼び出しが2回にわたりオブジェクトの型に応じて動的に決定される仕組みも存在します
- これをダブルディスパッチと呼びます

ここで例を挙げてみます。

私（Haroya）と学生Aは、勉強した科目の試験を受けるつもりです。

```java
public class StudentA {
    public void exam(String subject) {
        if (subject.equals("science")) {
            System.out.println("A is taking the science exam.");
        } else if (subject.equals("math")) {
            System.out.println("A is taking the math exam.");
        } else {
            System.out.println("A is not taking exam.");
        }
    }
}

public class StudentHaroya {
    public void exam(String subject) {
        if (subject.equals("science")) {
            System.out.println("Haroya is taking the science exam.");
        } else if (subject.equals("math")) {
            System.out.println("Haroya is taking the math exam.");
        } else {
            System.out.println("Haroya is not taking exam.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        StudentHaroya haroya = new StudentHaroya();
        StudentA a = new StudentA();

        haroya.exam("science");
        a.exam("math");
    }
}
```
次のように実装すると、以下のような問題が発生します。

1. 新しい科目が追加されるたびに、すべての学生クラスの `if` 文を修正する必要があります
2. 変更に対してオープンで、拡張に対してクローズドなコードになります。（OCP 違反）
3. ヒューマンエラーを引き起こすリスクがあります
4. コンパイル時に型の安全性を保証できません

```java
public abstract class Subject {
    public abstract void takeExam(Student student);
}

public class ScienceExam extends Subject {
    @Override
    public void takeExam(Student student) {
        student.takeScienceExam(this);
    }
}

public class MathExam extends Subject {
    @Override
    public void takeExam(Student student) {
        student.takeMathExam(this);
    }
}

// 学生クラス
public abstract class Student {
    abstract void takeScienceExam(ScienceExam exam);
    abstract void takeMathExam(MathExam exam);
}

public class StudentA extends Student {
    @Override
    public void takeScienceExam(ScienceExam exam) {
        System.out.println("A is taking the science exam");
    }

    @Override
    public void takeMathExam(MathExam exam) {
        System.out.println("A is taking the math exam");
    }
}

public class StudentHaroya extends Student {
    @Override
    public void takeScienceExam(ScienceExam exam) {
        System.out.println("Haroya is taking the science exam");
    }

    @Override
    public void takeMathExam(MathExam exam) {
        System.out.println("Haroya is taking the math exam");
    }
}

public class Main {
    public static void main(String[] args) {
        Student haroya = new StudentHaroya();
        Student a = new StudentA();
        
        Subject scienceExam = new ScienceExam();
        Subject mathExam = new MathExam();
        
        scienceExam.takeExam(haroya);  // 1. scienceExam の実際の型に基づいて最初のディスパッチが行われる
        mathExam.takeExam(a);        // 2. student の実際の型に基づいて2回目のディスパッチが行われる
    }
}
```
## ダブルディスパッチの動作原理

1. **最初のディスパッチ**
   - `scienceExam.takeExam(haroya)` を呼び出すとき
   - 実行時に実際の Subject 型（ScienceExam）に応じて適切な `takeExam` メソッドが選択されます

2. **2回目のディスパッチ**
   - `student.takeScienceExam(this)` を呼び出すとき
   - 実行時に実際の Student 型（StudentHaroya）に応じて適切な `takeScienceExam` メソッドが選択されます

## ダブルディスパッチの利点

1. **型の安全性**
   - コンパイル時に型チェックが可能
   - 実行時エラーの可能性が減少

2. **拡張性**
   - 新しい科目や学生タイプの追加が容易
   - 既存のコードを修正せず、新しいクラスを追加するだけで機能を拡張可能

3. **保守性**
   - 条件文を排除し、コードの可読性が向上
   - 各クラスの責任が明確になる

4. **OCP（Open-Closed Principle）の遵守**
   - 既存コードを修正することなく、新機能を追加可能

ダブルディスパッチはビジターパターン（Visitor Pattern）の核心メカニズムであり、複雑なオブジェクト構造において型に応じた処理を柔軟に実現することができます。

Visitor Pattern については、後日記述する予定です。

# 抽象クラス 
- インスタンスを生成できないクラス
    - 自体では存在できません
- 抽象クラスは抽象メソッドを持つことができます
    - 子クラスで必ず実装する必要があります

```java
abstract class Human {
    // 抽象メソッド（子クラスで実装する必要があります）
    abstract void old();

    // 通常のメソッド（子クラスで継承されます）
    void sleep() {
        System.out.println("zzz");
    }
}
```

### インターフェースとの違い
| 特性                  | 抽象クラス (Abstract Class)                   | インターフェース (Interface)                         |
|-----------------------|-----------------------------------------------|-----------------------------------------------|
| **インスタンス化**         | 不可能（直接オブジェクトを作成できない）       | 不可能（直接オブジェクトを作成できない）       |
| **抽象メソッド**        | 抽象メソッドを持つことが可能                   | すべてのメソッドはデフォルトで抽象メソッド（Java 8以降では default メソッドも可能） |
| **多重継承**          | 不可能（単一継承のみ可能）                    | 可能（多重継承をサポート）                    |
| フィールド            | 変数（フィールド)を持つことが可能（ただし、static と final フィールドのみ） | 変数（フィールド）**を持つことはできない（定数のみ可能、public static final のみ） |
| メソッド実装       | 通常のメソッドと抽象メソッドの両方を持つことが可能 | 基本的にすべてのメソッドは抽象メソッド（Java 8以降では default メソッドも可能） |
| **コンストラクタ**            | コンストラクタを持つことが可能                 | コンストラクタを持つことはできない              |


```java
interface MyInterface {
    void doSomething();  // 抽象メソッド

    // default メソッド
    default void defaultMethod() {
        System.out.println("再定義");
    }
}
```
- 再定義を通じて必ず実装しなくても、デフォルトの実装を提供することができます
- `static` メソッドはオーバーライドできません

```java
interface MyInterface {
    static void staticMethod() {
        System.out.println("This is a static method in the interface");
    }
}

class MyClass implements MyInterface {
    // staticMethod() は再定義（オーバーライド）することはできません。
    // 以下のコードはコンパイルエラーになります。
    // @Override
    // void staticMethod() { } // エラー発生: static メソッドはオーバーライドできません
}

public class Main {
    public static void main(String[] args) {
        MyInterface.staticMethod();  // インターフェースの static メソッドを呼び出し
    }
}
```
- なぜ子クラスで `static` メソッドをオーバーライドできないのか？
    - 静的メソッド（`static` メソッド）はクラスメソッドであり、クラスレベルで呼び出されます。
    - インスタンスとは無関係に動作するため、インスタンスレベルの動作であるオーバーライドとは概念的に異なります


# final
- `final` は一度しか値を割り当てることができず、宣言時に値を初期化しない場合、空の `final` 変数（blank final variable）と見なされます（値の変更は禁止）
    - 空の初期化変数は必ず明示的に初期化する必要があります
    - もし `final` 変数が初期化されていない場合、コンパイルエラーが発生します

### final の初期化方法
- 宣言と同時に初期化する
```java
final String name = "Haroya";
```

- コンストラクタまたは初期化ブロックで初期化する
```java
final String name;

Example() {
    name = "Haroya";
}
```

```java
public class Example {
    final String name;

    {
        name = "Haroya";
    }
    
    public Example(){
    }
}
```
- `final` クラスはサブクラスを持つことができません **（継承が禁止されます）**
    - final クラス内のすべてのメソッドは自動的に `final` となります

- `final` メソッドとして宣言すると、サブクラスがそのメソッドをオーバーライドすることはできません（オーバーライド禁止）


# Object
- `java.lang` に属しているため、`import` しなくても自動的に含まれます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/f201df44-796c-be96-894b-479fa056525e.png)
 
通常であれば、このように `static` メソッドを呼び出す必要があります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/4e93b85a-75d8-547e-1cd6-371d77f855e5.png)

しかし、`java.lang` に属するクラスは次のように `import` なしで使用できます。

- Object はすべてのクラスの最上位クラスです
    - 明示的に `extend Object` を記述しなくても使用されます

```java
public class Main {

  static class Haroya{
    public void sayHello(){
      System.out.println("hello");
    }
  }

  public static void main(String[] args){
    Haroya haroya = new Haroya();
    haroya.sayHello();
  }
}
```
次のように定義されたクラスのバイトコードを見ると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/44a14239-6d5c-d2ab-8b15-f13628e503a8.png)

次のように `Object` のコンストラクタが呼び出されていることがわかります


## 主なメソッド
| メソッド名                  | 説明                                                                                           |
|----------------------------|----------------------------------------------------------------------------------------------|
| **clone()**                | オブジェクトを複製して新しいオブジェクトを返します。（ただし、Cloneable インターフェースを実装する必要があります。） |
| **equals(Object obj)**     | 2つのオブジェクトが等しいかどうかを比較します。デフォルトでは参照を比較しますが、オーバーライドして値を比較できます。 |
| **finalize()**             | オブジェクトが **GC（ガベージコレクション）** によって削除される前に呼び出されます。（推奨されません）          |
| **getClass()**             | オブジェクトのランタイムクラス情報を返します。                                                  |
| **hashCode()**             | オブジェクトのハッシュコードを返します。`equals()` が true のオブジェクトは同じハッシュコードを持つべきです。  |
| **notify()**               | 同じオブジェクトを待機しているスレッドのうち1つを起こします。                                    |
| **notifyAll()**            | 同じオブジェクトを待機しているすべてのスレッドを起こします。                                     |
| **toString()**             | オブジェクトの文字列表現を返します。デフォルト実装はクラス名とハッシュコードを返します。          |
| **wait()**                 | 現在のスレッドを待機状態にします。                                                            |
| **wait(long timeout)**     | 指定された時間、現在のスレッドを待機状態にします。                                              |
| **wait(long timeout, int nanos)** | 指定された時間とナノ秒の間、現在のスレッドを待機状態にします。                               |


# 래퍼런스

https://refactoring.guru/ko/design-patterns/visitor-double-dispatch

https://docs.oracle.com/javase/specs/jls/se23/html/jls-8.html#jls-8.8.7.1