【Java】インナークラスについて（メンバークラス編）

Java Goldの勉強も兼ねて。Java 17を基準にしています。

# はじめに
Javaのインナークラスは、[言語仕様](https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.1.3)によると以下のように定義されています。

* 明示的または暗黙的に`static`**でない**メンバークラス
* 暗黙的に`static`**でない**ローカルクラス
* 匿名クラス

メンバークラスについて、ネットに転がっている記事や書籍の一部では`static`なメンバークラスについてもインナークラス（staticインナークラス）と呼称していることがありますが、厳密には誤りです。<br>
本記事では1つ目のメンバークラスについて取り上げます。
2つ目の[ローカルクラス](https://qiita.com/KaZu_rtr/items/1da97b077d210841e85e)、3つ目の[匿名クラス](https://qiita.com/KaZu_rtr/items/466ec6ff97b4f09ba4f2)についても取り上げています。

:::note info
便宜上、1つ目の定義に当てはまるものをすべて「インナークラス」と呼称しています。
:::

:::note info
staticなメンバークラスは定義上インナークラスではありませんが、ここでは一緒に説明します。
:::

# 記法
以下のように書けます。
```java:Outer.java
// （インナークラスに対する）エンクロージングクラス
public class Outer {
	// インナークラスは外部クラスのメンバーとして定義されるクラス。
	public class Inner {
		public void display() {
			System.out.println("Inner class method called.");
		}
	}

	class PackageProtectedInner {
		public void display() {
			System.out.println("Package protected inner class method called.");
		}
	}

	// エンクロージングクラスとは異なり、protectedなクラスを定義可能。
	protected class ProtectedInner {
		public void display() {
			System.out.println("Protected inner class method called.");
		}
	}

	// privateも定義可能。
	private class PrivateInner {
		public void display() {
			System.out.println("Private inner class method called.");
		}
	}

	// staticなメンバークラスはインナークラスではない。
	public static class StaticMember {
		public void display() {
			System.out.println("Static member class method called.");
		}
	}

}

```
基本的なクラスにネストした形でクラスを書きます。`public`やアクセス修飾子なし（`package protected`）だけでなく、`protected`や`private`なクラスも記述できます。
# 基本的な利用方法
フィールドや関数にいきなりアクセスできないのと同じように、非staticなメンバークラスのアクセスについてもエンクロージングクラスのインスタンスが必要です。また、`static`なメンバーにはインスタンスが不要なように、`static`なメンバークラスへのアクセスにはエンクロージングクラスのインスタンスは必要ありません。<br>
先ほどの例を利用すると以下のようになります。

```java:Main.java
public static void main(String[] args) {
	final var inner = new Outer().new Inner();
	// final var packageProtectedInner = new Outer().new PackageProtectedInner(); // 同一パッケージでは可能。
	// final var protectedInner = new Outer().new ProtectedInner(); // 同一パッケージでは可能。継承先の場合は制約あり。
	// final var privateInner = new Outer().new PrivateInner(); // 不可。
	final var staticMember = new Outer.StaticMember();

	inner.display();
	// packageProtectedInner.display();
	// protectedInner.display();
	// privateInner.display();
	staticMember.display();
}
```
エンクロージングクラスを`new`した後インナークラスを`new`します。`static`なメンバークラスの場合、エンクロージングクラスのインスタンスは不要なため、`new Outer.StaticMember()`のように書けばよいです（`Outer.new StaticMember()`ではないので注意）。また、`static`なメンバークラスを明示的にインポートすることで、通常のクラスのように`new`することもできます。ただし、実用上は混乱を招く可能性があるので避けた方が良いと思います。
```java:Main.java
import com.whiteboard.innerclass.Outer.StaticMember;

public class Main {
	public static void main(String[] args) {
		final var staticMember = new StaticMember();
		staticMember.display();
    }
}
```
`protected`なインナークラスについては少し注意が必要です。`protected`で修飾されたメンバーは同一パッケージまたはサブクラスで利用可能になりますが、クラスは明示的な実装をしない限りコンストラクタも同じアクセス修飾子になるため、今回の場合はコンストラクタは`protected`になっています（[デフォルトコンストラクタの仕様](https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.8.9)）。そのため、サブクラスからインスタンスを作成することができず、実質的に利用不可能となります。なお、明示的に`public`なコンストラクタを定義した場合はインスタンスを作成して利用することが可能です。<br>
`protected`コンストラクタへのアクセスについては[言語仕様に独立した項](https://docs.oracle.com/javase/specs/jls/se17/html/jls-6.html#jls-6.6.2.2)があります。簡単に言うと`new`できるのは同一パッケージ内だけということです。<br>
`private`なインナークラスはエンクロージングクラスからのみアクセス可能です。`static`ではないため、利用には通常のクラスと同様にインスタンスの生成が必要です。<br>
インナークラスからはインスタンスの生成なしにエンクロージングクラスの（非staticな）各メンバーに直接アクセス可能です。これはインナークラスがエンクロージングクラスのインスタンスに紐づいているためです。

```java:OuterWithPrivateInner.java
public class OuterWithPrivateInner {

	private String name = "Outer Class";
	private int id = 0;

	public void display() {
		final var inner = new PrivateInner();
		System.out.println(inner.innerName);
		System.out.println(inner.id);
		// System.out.println(innerName); // インナークラスのフィールドはエンクロージングクラスからは参照できない
		// System.out.println(PrivateInner.this.id); // 上記と同じ
		inner.display();
		inner.displayOuter();
		inner.hi();
	}

	private void hello() {
		System.out.println("Hello from Outer class!");
	}

	private class PrivateInner {
		private String innerName = "Private Inner Class";
		private int id = 1;
		private void display() {
			System.out.println("Private inner class method called.");
		}
		private void displayOuter() {
			// 同名のフィールドが他に無ければフィールド名のみで参照可能
			System.out.println("Outer class name: " + name);
			// 同名のフィールドがある場合は、エンクロージングクラス名.this.フィールド名で参照する必要がある
			System.out.println("Outer class id: " + OuterWithPrivateInner.this.id);
		}
		private void hi() {
			// メソッドも参照可能
			hello();
			System.out.println("Hello from Private inner class!");
		}
	}
}
```

# 制約
Java15まではインナークラスのメンバーや初期化子に`static`を使用することができませんでした（`static final`な定数変数は可能）。[言語仕様](https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.1.3)に記載されている例をそのまま引用しますが、以下はJava15までのバージョンではコンパイルが通りません。`Inner`クラスを`static`にすればコンパイルできるようになりますが、インナークラスという扱いではなくなります。

```java
class HasStatic {
    static int j = 100;
}

class Outer {
    class Inner extends HasStatic {
        static {
            System.out.println("Hello from Outer.Inner");
        }

        static       int x = 3;
        static final int y = 4;

        static void hello() {
            System.out.println("Hello from Outer.Inner.hello");
        }

        static class VeryNestedButNotInner
            extends NestedButNotInner {}
    }
    
    static class NestedButNotInner {
        int z = Inner.x;
    }
    
    interface NeverInner {}  // Implicitly static, so never inner
}
```
# 具体的な利用例
機能として結びつきが強いものをインナークラスにまとめるというのが基本的な使い方になります。
## ヘルパークラス
特定のクラス内で補助機能を提供する際に利用することができます。以下はクラスで保持しているデータリストに対して処理を提供するコードの例です。
```java:DataProcessor.java
public class DataProcessor {
    private List<String> data;
    
    public DataProcessor(List<String> data) {
        this.data = data;
    }
    
    public List<String> processData() {
        DataSorter sorter = new DataSorter();
        return sorter.sort();
    }
    
    // データソート専用のヘルパークラス
    private class DataSorter {
        public List<String> sort() {
            // 外部クラスのdataフィールドに直接アクセス
            Collections.sort(data);
            return data;
        }
    }
}
```
別途処理を追加したりソート方法を変更したりする場合はDataSorterに修正を加えたり新しいヘルパークラスを追加したりする想定です。

## APIのカスタム実装の提供
既存のAPIに対して何らかのカスタマイズを行いたい場合に利用することができます。以下はイテレータにカスタムを施して提供するコードの例です。
```java:CustomCollection.java
import java.util.Iterator;

/* public */ class CustomCollection<T> {
    private List<T> elements = new ArrayList<>();

    public void add(T element) {
        elements.add(element);
    }

    public Iterator<T> iterator() {
        return new CustomIterator();
    }

    // イテレータの実装
    private class CustomIterator implements Iterator<T> {
        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < elements.size();
        }

        @Override
        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elements.get(currentIndex++);
        }
    }
}

// 利用例
public class Main {
	public static void main(String[] args) {
		CustomCollection<String> collection = new CustomCollection<>();
		collection.add("Apple");
		collection.add("Banana");
		collection.add("Cherry");

		Iterator<String> iterator = collection.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
    }
}
```
このように実装することで独自のイテレータを提供できます。また、上記例はデザインパターンのComposite、Adaptor（、Iterator）が実現されています。
## ビルダーの提供
こちらは`static`メンバークラスを使った実装ですが、「インナークラスの利用例」としてよく見かけるので一応紹介します。
```java:Main.java
/* public */ class Person {
	private final String firstName;
	private final String lastName;
	private final int age;
	private final String address;
	private final String phone;

	private Person(Builder builder) {
		this.firstName = builder.firstName;
		this.lastName = builder.lastName;
		this.age = builder.age;
		this.address = builder.address;
		this.phone = builder.phone;
	}

	// getterは割愛

	// ビルダーとしての内部クラス
	public static class Builder {
		// 必須パラメータ
		private final String firstName;
		private final String lastName;

		// オプションパラメータ
		private int age = 0;
		private String address = "";
		private String phone = "";

		public Builder(String firstName, String lastName) {
			this.firstName = firstName;
			this.lastName = lastName;
		}

		public Builder age(int age) {
			this.age = age;
			return this;
		}

		public Builder address(String address) {
			this.address = address;
			return this;
		}

		public Builder phone(String phone) {
			this.phone = phone;
			return this;
		}

		public Person build() {
			return new Person(this);
		}
	}

	@Override
	public String toString() {
		return firstName + " " + lastName + ", age: " + age +
			", address: " + address + ", phone: " + phone;
	}
}

// 利用例
public class Main {
	public static void main(String[] args) {
		Person person = new Person.Builder("山田", "太郎")
				.age(30)
				.address("東京都渋谷区")
				.phone("03-1234-5678")
				.build();
		System.out.println(person);
    }
}
```
エンクロージングクラスのインスタンスを生成するためのクラスを内包させます。このように実装するメリットとして以下が挙げられます。

1. パラメータの追加に対して柔軟に対応できる
2. 必須・任意の区別をつけやすい
3. 利用側はメソッドチェーンを利用して必要な関数を呼び出して実装することができ、コード量が減少する

1と2について、例えば`Person`クラスにコンストラクタで値を決定する実装がされていた場合、パラメータが多ければ多いほどコントラクタの引数が長大になり、可読性が低下したりミスが発生しやすくなったりします。また、新しいパラメータを追加する際にはコンストラクタの追加または修正が必要になるので、変更コストやバグを作りこむリスクも高くなります。かと言ってgetterとsetterを使った実装にするとイミュータブルになってしまうため、不変データを扱う場合には不向きです。<br>
一方、ビルダーを提供する実装であれば、データオブジェクトのコンストラクタ引数の問題は解決することができ、利用側は気にする必要がなくなります。上記例にはありませんが、必須パラメータに検査ロジックを組み込んでおけば確実に値の存在を保証することができます。また、エンクロージングクラスはgetterのみ提供すればイミュータブルなオブジェクトとして扱うことができるためバグのリスクは低下します。

# まとめ

* 明示的または暗黙的に定義された`static`でないメンバークラスはインナークラスの一種である
* 通常クラスと異なり、`protected`や`private`でも定義できる
* Java 15まではインナークラスのメンバーに`static`を付与することができない（`static final`な定数変数を除く）
* インナークラスはエンクロージングクラスと機能としての結びつきが強いものが実装されるのが好ましい