Java ネストしたクラス

Java 11 Gold 取得に向けた学習記録

# Enclosingクラス
クラスの内部でさらにクラスを定義した場合、外側のクラスは**Enclosingクラス**と呼ばれ、内側のクラスは**ネストしたクラス**と呼ばれる（enclose=囲う）。

いくつかの分類がある。

|  | 定義場所 | `public` | `protected` | `private` | `abstract` | `final` | `stataic` |
|:-:|-:|:-:|:-:|:-:|:-:|:-:|:-:|
| インナークラス | クラス内 | ◯ | ◯ | ◯ | ◯ | ◯ | ◯ |
| ローカルクラス | メソッド内 | × | × | × | ◯ | ◯ | × |
| 匿名クラス | メソッド内 | × | × | × | × | × | × |

# インナークラス

クラス内で定義されたクラスを**インナークラス**と言う。

```java:インナークラス
class OuterClass {
    class InnerClass {
    
    }
}
```

インナークラスはEnclosingクラスと同様のアクセス修飾子（`public`, `protected`, `private`, `abstract`, `final`）を使用する事ができる。

またインターフェースを実装（`implements`）することもできる。

インナークラスはEnclosingクラスのインスタンスに属しているため、Enclosingクラスのインスタンスを介してアクセスを行う。

```java:
OuterClass outer = new MyClass();
OuterClass.InnerClass inner = outer.new InnerClass();
```

以下のように1行にまとめて記述することもできる。

```java:
OuterClass.InnerClass inner = new OuterClass().new InnerClass();
```

また`import`を行えば、このような記述も可能。

```java:インナークラスのimportもできる
import my.package.OuterClass.InnerClass;

InnerClass inner = new OuterClass().new InnerClass();
```

## 特徴

インナークラスはEnclosingクラスで定義されたフィールドやメソッドと同様に、**Enclosingクラスのインスタンス変数に直接アクセスする事ができる**（ここが個人的に最初全然理解できていなかったところで、インナークラスを理解する上での肝だと思った）。


```java:Enclosingクラスのインスタンス変数に直接アクセスできる
class OuterClass {
    String outerField = "hello";

    class InnerClass {
        String innerFiled = outerField + " world";
    }
}
```

この特徴からわかるように、インナークラスはEnclosingクラスのインスタンスに依存している。

:::note info
インナークラスはEnclosingクラスのインスタンスに依存している
:::

## `static`インナークラス
インナークラスを`static`修飾したものを特に**staticインナークラス**と呼ぶ。

```java:staticインナークラス
class OuterClass {
    static class InnerClass {

    }
}
```

`static`インナークラスは、インスタンス化にEnclosingクラスのインスタンスを必要としない。

個人的に混同していたのが、`static`インナークラスはあくまでも「クラス」なので、インスタンス化（`new`）が不要なわけではない。

:::note info
`static`インナークラスは`static`だからと言って`new`しなくても良いわけではない
:::


```java
OuterClass.InnerClass inner = new OuterClass.InnerClass();
```

以下のような記述も可能。

```java:staticインナークラスもimportできる
import my.package.OuterClass.InnerClass;

InnerClass inner = new InnerClass();
```

`static`インナークラスはEnclosingクラスのインスタンスではなく、Enclosingクラス自体に属するため、Enclosingクラスのインスタンスに依存しない。

そのため、Enclosingクラスの非`static`フィールドや、非`static`メソッドにはアクセスする事ができない。

```java
class OuterClass {
    String outerField = "hello";

    static class InnerClass {
        // コンパイルエラー
        String innerFiled = outerField + " world";
    }
}
```

:::note info
`static`インナークラスはEnclosingクラスのインスタンスに依存しない。
Enclosingクラス自体に依存している。
:::

## インナークラスで`static`フィールドは定義できない

非`static`インナークラス内で`static`なフィールドを定義しようとした場合、コンパイルエラーが発生する。

```java
class OuterClass {
    class InnerClass {
        // コンパイルエラー
        static int staticField = 0;

        // 定数は定義可能
        static final int CONSTANT_FIELD = 10;
    }  
}
```

`static`フィールドはクラス自体に依存しているが、非`static`インナークラスはEnclosingクラスのインスタンスに依存している。

ここでの`staticField`は、`InnerClass`のインスタンスではなくクラス自体に依存する。一方で、`InnerClass`は`OuterClass`のインスタンスに依存している。そのため、依存関係が一致していない。

そのため、インナークラスを`static`修飾しなければならない。

```java:
class OuterClass {
    static class InnerClass {
        static int staticField = 10;
    }        
}
```

# ローカルクラス

メソッド内で定義されるクラスを**ローカルクラス**と言う。

（メソッド内で定義される変数を**ローカル変数**と言う）

```java:ローカルクラス
class OuterClass {
    void outerMethod() {
        class LocalClass {
        
        }
    }
}
```

ローカルクラスは自身が定義されたスコープ内でのみ、インスタンス化を行う事ができる。

```java
class OuterClass {
    void outerMethod() {
        class LocalClass {
           
        }

        LocalClass local = new LocalClass();
    }
}
```


Factoryパターンを使用する際に、クラス定義をメソッド内に隠蔽させる使い方などがある。


https://qiita.com/AsahinaKei/items/3a1f17b728769bab95b7

ローカルクラスのインスタンスは、メソッドの実行後もメモリ上に存在し続ける可能性がある。

```java
interface MyInterface {
    void myMethod();
}
```

```java
class OuterClass {
    MyInterface outerMethod() {
        class LocalClass implements MyInterface {
            @Override
            public void myMethod() {
            }
        }

        return new LocalClass();
    }
}
```

```java
MyInterface local = new OuterClass().outerMethod();
// outerMethod()実行後も、LocalClassのインスタンスはまだ生きている
local.myMethod();    
```

一方で、ローカル変数のスコープはメソッドの終了とともに消滅する（メモリ上から削除される）。

そのためローカルクラス内でEnclosingクラスのローカル変数を使用した場合、実質的に`final`（effectively final）な定数扱いになる（<a href="https://qiita.com/AsahinaKei/items/5b7bd47576cf005cc6bd#%E3%83%A9%E3%83%A0%E3%83%80%E5%BC%8F%E5%86%85%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%A4%89%E6%95%B0%E3%81%AF%E5%AE%9F%E8%B3%AA%E7%9A%84%E3%81%ABfinal%E3%81%A7%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84" target="_blank" rel="noopener noreferrer">ラムダ式と同様</a>）。

```java
class OuterClass {
    void outerMethod() {
        String localVal = "hello";
        class LocalClass {
            void innerMethod(){
                // コンパイルエラー
                localVal = "world";
            }
        }
    }
}
```

JavaScriptでは**クロージャー**と呼ばれる外側のスコープ（レキシカルスコープ）を保持し続ける関数を定義する事ができる。

```javascript:JavaScript
function outer() {
    // レキシカルスコープ
    let outerVar = "hello";
    
    function inner() {
        outerVar = "world";
        console.log(outerVar);
    }
    
    return inner;
}

const innerFn = outer();
innerFn();  
// >> "world"
```

# 匿名クラス

名前のないクラスのインスタンスを生成するための特殊な構文。

普通、Javaのクラスは名前とともに宣言され、宣言された名前と`new`キーワードを使ってインスタンス化が行われる。

```java:インスタンス化
MyClass obj = new MyClass();
```

匿名クラスは宣言と同時にインスタンスが生成される。名前を持たないクラスから生成されるため、クラス名を用いて宣言する必要があるコンストラクタを定義することはできない。

:::note info
匿名クラスにはコンストラクタを定義できない
:::


また、匿名クラスはインターフェースに使用した場合にはインターフェースを「実装（`implements`）」したクラスになり、通常のクラスに使用した場合にはそのクラスを「継承（`extends`）」したサブクラスになる。

## インターフェースに対して使用する匿名クラス

```java:インターフェースに対して使用する例
public interface MyInterface {
    void method();
}
```

```java:インターフェースに対して使用する例
public class Main {
    public static void main(String[] args) {
        MyInterface obj = new MyInterface() {
            // ここが MyInterface を「実装」するクラス
            @Override
            public void method() {
                System.out.println("ここでインターフェースを実装する");
            }
        };
    }
}
```

スレッドの生成時にも利用される。

```java
// java.lang.Thread、java.lang.Runnable を使用

// 匿名クラスを使ってRunnableインターフェースを実装する
var thread = new Thread(new Runnable(){
    @Override
    public void run() {
        System.out.println("非同期的に処理を実行します。");
    }
});

thread.start();
// >> 非同期的に処理を実行します。
```

## クラスに対して使用する匿名クラス


```java:クラスに対して使用する例
public class SuperClass { 
    public void method() {
        System.out.println("hello world");
    }
}
```

```java:クラスに対して使用する例
public class Main {
    public static void main(String[] args) {
         SuperClass obj = new SuperClass() {
            // ここが SuperClass を「継承」する匿名クラス
            @Override
            public void method() {
                System.out.println("ここでスーパークラスを継承する");
            }
        };
    }
}
```

匿名クラスは、クラス名やコンストラクタは定義する事ができないため、初期化を行いたい場合インスタンス初期化子を使用する。

インスタンス初期化子についてはこちら。

https://qiita.com/AsahinaKei/items/5b7bd47576cf005cc6bd

```java:インスタンス初期化子
public class SuperClass { 
    String field;
    
    public void method() {
        System.out.println(field);
    }
}
```

```java:インスタンス初期化子
public class Main {
    public static void main(String[] args) {
        SuperClass obj = new SuperClass() {
            // インスタンス初期化子
            {
                field = "hello world";
            }

            @Override
            public void method() {
                System.out.println(field);
            }
        };

        obj.method();
        // >> hello world
    }
}

```

`List`の初期化では匿名クラスの初期化子が利用されている。

```java
List<String> list = new ArrayList<String>(){
    // ここが匿名クラス部分
    {
        // ここが匿名クラスのインスタンス初期化子部分
        add("Apple");
        add("Orange");
        add("Melon");
    }
};
```


# 参考
・<a href="https://amzn.asia/d/gkx4D5q" target="_blank" rel="noopener noreferrer">徹底攻略Java SE 11 Gold問題集[1Z0-816]対応</a>