【Java発展学習2日目】ジェネリクス・列挙型・インナークラスとは

# ジェネリクス(generics)
[参考1: 境界ワイルドカード型](https://qiita.com/ysn/items/66e225004bf656f012c7)
`型制約`のない`汎用クラス`を提供する`安全機構`。
`ジェネリクス`によって、`クラス`や`静的メソッド`で取り扱う`データ`を`型制約`を行わずに定義することができる。

なお、`ジェネリクス`が採用された`静的メソッド`は`ジェネリックメソッド`と呼ばれ、
`返却型`の直前に`ジェネリクス`を置くことで、`返却型`・`引数の型`・`処理ブロック`で`ジェネリクス`を利用することができる。

```java:ジェネリックメソッド(例)
public static <T> List<T> asList(T... a)
```

ただし、`ジェネリクス`の利用には以下の3つの制約が存在する。

> 1. `ジェネリクス`の型として、`基本データ型`は利用不可
> <br>→ `値型`である`基本データ型`は型が**一意**であるため
> 2. `ジェネリクス`を用いたクラスの`配列`は生成不可
> <br>→ `配列`自体の`型制約`がなくなってしまうため
> 3. `Throwable`クラスの`下位クラス`(=`例外クラス`)では利用不可
> <br>→ `Error`クラスや`検査/非検査例外`には**大きな違い**があるため

## サンプルコード(ジェネリクス)

```java:Generics.java
public class Generics {
    public static void main(String[] args) {
        // NumberのサブクラスであるInteger型
        // -> GenericsTest1は<E extends Number>型であるため、
        //    "Number"クラスとそのサブクラスのインスタンスが生成可能
        GenericsTest1<Integer> gen1 = new GenericsTest1<>();
        GenericsTest1<Float> gen2 = new GenericsTest1<>();
        GenericsTest1<Number> gen3 = new GenericsTest1<>();
        // 実型引数の型ワイルドカードを用いることも可能(後述)
        GenericsTest1<? extends Number> gen4 = new GenericsTest1<Long>();
        GenericsTest2<? super Number> gen5 = new GenericsTest2<>();

        // インスタンスgen1は<Integer>型なので、Integer型整数の代入が可能
        gen1.setData(Integer.valueOf(123));
        Integer i = gen1.getData();
        System.out.println(i);

        // インスタンスgen2は<Float>型なので、Float型小数の代入が可能
        gen2.setData(Float.MIN_VALUE);
        // Float型データをfloat型として取得することも可能
        float f = gen2.getData();
        System.out.println(f);

        // インスタンスgen3は<Number>型なので、Double型小数の代入が可能
        gen3.setData(Double.MIN_VALUE);
        // Number型 -> Double型 へのダウンキャストは「明示的」に行う
        double d = (Double) gen3.getData();
        System.out.println(d);

        // インスタンスgen4は<? extends Number>型だが、"Number"およびそのサブクラスの代入は不可能
        // -> "Number"およびそのサブクラス -> <? extends Number>型 への変換は不可能
        // gen4.setData(Long.valueOf(35005L));

        // インスタンスgen5は<? super Number>型だが、"Number"およびそのサブクラスの代入が可能
        // -> "Number"およびそのサブクラス -> <? super Number>型 への変換は可能
        gen5.setData(Long.valueOf(27593405L));
        // ただし、<? super Number>型は「"Number"クラスとその全ての上位クラス」を表すため、
        // "Object型"としてのみ読み込み可能
        Object l = gen5.getData();
        System.out.println(l);
    }
}

// ジェネリクスを"Number"またはそのサブクラスに制限したクラス
class GenericsTest1<E extends Number> {
    private E data;

    public void setData(E data) {
        this.data = data;
    }

    public E getData() {
        return this.data;
    }
}

// ジェネリクスを制限しないクラス
class GenericsTest2<E> {
    private E data;

    public void setData(E data) {
        this.data = data;
    }

    public E getData() {
        return this.data;
    }
}
```

```java:実行結果
123
1.4E-45
4.9E-324
27593405
```

***

# 列挙型(enumeration)
`列挙型`プロパティを用いることによって、指定した種類の値(=`列挙子`)のみ代入できる。
代入時は`<列挙型識別子>.<列挙子>`で記述するが、`静的インポート`を行うことで`列挙型識別子`の省略が可能。

## サンプルコード(列挙型)

```java:Enumeration.java
public class Enumeration {
    public static void main(String[] args) {
        ABCStr abcStrA = new ABCStr(ABC.A);
        ABCStr abcStrB = new ABCStr(ABC.B);
        ABCStr abcStrC = new ABCStr(ABC.C);

        System.out.println(abcStrA);
        System.out.println(abcStrB);
        System.out.println(abcStrC);
    }
}

// 安全に値を制約する列挙型
// -> 定義した列挙子以外の入力は許されない
// <- アクセス修飾子として"public"を付与すると、同一ソースファイルに"public"でアクセス修飾子が複数存在する場合、
//    「実行時」には2番目以降の"public"が付与された識別子は認識されないため、値がnullとなることに注意
enum ABC {
    A, B, C;
}

class ABCStr {
    private ABC abc;

    // コンストラクタ
    public ABCStr(ABC abc) {
        this.abc = abc;
    }

    @Override
    public String toString() {
        // 継承先で同名プロパティが存在した場合に、クラスABCStrのプロパティを参照するため
        // "this"キーワードを付与
        return "Value: " + this.abc;
    }
}
```

***

# 3種類のインナークラス(inner class)
`クラス宣言ブロック`内で定義される`インナークラス`には、以下の3種類が存在する。

|クラス種別|内容|
|:--|:--|
|`メンバクラス(member class)`|`クラスブロック`で宣言|
|`ローカルクラス(local class)`|`メソッドブロック`で宣言|
|`匿名クラス`|`new`文で宣言|

## サンプルコード(3種類のインナークラス)

```java:ThreeClassType.java
public class ThreeClassType {
    public static void main(String[] args) {
        // 静的メンバクラスオブジェクトの生成
        // -> 外部クラスから抽出してオブジェクトを生成可能
        Outer.InnerStaticMember ism = new Outer.InnerStaticMember();
        ism.innerStaticMemberMethod();

        // 外部クラスの静的メソッドは外部クラスオブジェクトの生成が不要
        Outer.outerStaticMethod();

        // 「動的メンバクラスオブジェクトの生成」・「外部クラスの動的メソッド利用」時は、外部クラスオブジェクトの生成が必要
        Outer o = new Outer();
        o.outerMethod();

        // 動的メンバクラスオブジェクトは外部クラスオブジェクトを紐づけて生成
        // -> "<外部クラスオブジェクト>.new"で宣言
        Outer.InnerMember im = o.new InnerMember();
        im.innerMemberMethod();

        // ローカル変数(final)
        final int localFieldFinal = 0;
        // ローカル変数(非final)
        int localField = 0;

        // 匿名クラスの定義・匿名クラスオブジェクトの生成・匿名クラスメソッドの利用
        // -> "new文"で「親クラス」を指定して匿名クラスを定義
        // -> 「実質的にfinal」であるローカル変数(および外部クラスのフィールド)にのみアクセス可能
        new Object() {
            int anonymousField = 0;
            // 匿名クラスの動的メソッド
            // -> 匿名クラスはオブジェクトを即時生成するため利用可能
            void anonymousMethod() {
                ++anonymousField;
                System.out.println("local field(final) in Anonymous Class: " + localFieldFinal);
                System.out.println("local field(non-final) in Anonymous Class: " + localField);
                System.out.println("anonymousField in Anonymous Class: " + anonymousField);
            }
        }.anonymousMethod();
    }
}

// 外部クラス
class Outer {
    // 外部クラスの静的フィールド
    static int outerStaticField = 0;
    // 外部クラスの動的フィールド
    int outerField = 0;

    // 静的メンバクラス
    // -> 「クラスOuter」の一部であるため、Outerオブジェクトの生成は不要であり、クラスOuterから抽出して利用可能
    // -> Outerオブジェクトからは独立(=静的)しているため、「静的フィールド」にのみアクセス可能
    static class InnerStaticMember {
        void innerStaticMemberMethod() {
            ++outerStaticField;
            System.out.println("static field in Static Member Class: " + outerStaticField);
        }
    }

    // 動的メンバクラス
    // -> 「Outerオブジェクト」の一部であるため、Outerオブジェクトを生成し紐づける必要がある
    // -> 「Outerオブジェクト」と紐づいているため、「動的フィールド」にもアクセス可能
    class InnerMember {
        void innerMemberMethod() {
            ++outerStaticField;
            ++outerField;
            System.out.println("static field in Member Class: " + outerStaticField);
            System.out.println("field in Member Class: " + outerField);
        }
    }

    // 動的メソッド
    void outerMethod() {
        // ローカル変数(final)
        final int outerLocalFieldFinal = 0;
        // ローカル変数(非final)
        int outerLocalField = 0;

        // ローカルクラス
        // -> 「実質的にfinal」であるローカル変数(および外部クラスのフィールド)にのみアクセス可能
        class InnerLocal {
            void innerLocalMethod() {
                ++outerStaticField;
                ++outerField;
                System.out.println("local field(final) in Local Class(defined in Dynamic Method): " + outerLocalFieldFinal);
                System.out.println("local field(non-final) in Local Class(defined in Dynamic Method): " + outerLocalField);
                System.out.println("static field in Local Class(defined in Dynamic Method): " + outerStaticField);
                System.out.println("field in Local Class(defined in Dynamic Method): " + outerField);
            }
        }

        // ローカルクラスは定義したメソッド内でのみ利用可能
        InnerLocal il = new InnerLocal();
        il.innerLocalMethod();
    }

    // 静的メソッド
    static void outerStaticMethod() {
        // ローカル変数(final)
        final int outerLocalFieldFinal = 0;
        // ローカル変数(非final)
        int outerLocalField = 0;

        // ローカルクラス
        // -> 「実質的にfinal」であるローカル変数(および外部クラスのフィールド)にのみアクセス可能
        class InnerLocal {
            void innerLocalMethod() {
                ++outerStaticField;
                System.out.println("local field(final) in Local Class(defined in Static Method): " + outerLocalFieldFinal);
                System.out.println("local field(non-final) in Local Class(defined in Static Method): " + outerLocalField);
                System.out.println("static field in Local Class(defined in Static Method): " + outerStaticField);
            }
        }

        // ローカルクラスは定義したメソッド内でのみ利用可能
        InnerLocal il = new InnerLocal();
        il.innerLocalMethod();
    }
}
```

```java:実行結果(一部改変)
// 「静的メンバクラス」は外部クラスの「動的フィールド」にはアクセス不可
static field in Static Member Class: 1

// 「動的メンバクラス」は外部クラスの「動的フィールド」にアクセス可能
static field in Member Class: 4
field in Member Class: 2

// 「ローカルクラス」がアクセス可能な「ローカル変数」は「実質的にfinal」であるローカル変数のみ
local field(final) in Local Class(defined in Static Method): 0
local field(non-final) in Local Class(defined in Static Method): 0
// 「静的メソッドで定義されたローカルクラス」は外部クラスの「動的フィールド」にはアクセス不可
static field in Local Class(defined in Static Method): 2

local field(final) in Local Class(defined in Dynamic Method): 0
local field(non-final) in Local Class(defined in Dynamic Method): 0
static field in Local Class(defined in Dynamic Method): 3
// 「動的メソッドで定義されたローカルクラス」は外部クラスの「動的フィールド」にアクセス可能
field in Local Class(defined in Dynamic Method): 1

// 「匿名クラス」がアクセス可能な「ローカル変数」は「実質的にfinal」であるローカル変数のみ
local field(final) in Anonymous Class: 0
local field(non-final) in Anonymous Class: 0
anonymousField in Anonymous Class: 1
```

***

# Optional<T>クラス
`<T>型データ`の`null安全(null safety)`を実現するクラス。
なお、`Optional<T>`クラスは`new文`による`インスタンス`の生成は**禁止**である。

[前述](https://qiita.com/b150005/items/7dd49885dfd7b9e29425#%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9generics)したように、`ジェネリクス`に対する`基本データ型`の利用はできないため、
`基本データ型`データを扱う場合はその`ラッパクラス`を利用する。
ただし、**処理効率が悪い**ため、`ラッパクラス`専用の`Optional`クラスが以下のように用意される。

|基本データ型|Optionalクラス|
|:--|:--|
|`int`|`OptionalInt`|
|`long`|`OptionalLong`|
|`double`|`OptionalDouble`|

## 定義

```java:Optional<T>クラス
// Optional<T>クラスのインスタンス生成
Optional<T> Optional.ofNullable(T value)
// パラメータ
// value: nullableなT型データ

// nullチェック
// -> 中身がnullでなければtrue,nullであればfalseを返却
boolean Optional<T>.isPresent()

// データの取得
// -> 中身がnullであればNoSuchElementExceptionが発生
T Optional<T>.get()

// 中身がnullであれば指定データで置換
T Optional<T>.orElse(T other)
// パラメータ
// other: 中身がnullである場合に置換する置換先データ
```

## サンプルコード(Optional<T>クラス)

```java:OptionalNullSafety.java
import java.util.Optional;

public class OptionalNullSafety {
    // 引数のOptional<String>型データが「null」または「文字数0」であれば置換した文字列を返却
    static String nullCheck(Optional<String> str) {
        // 引数strがnullまたは文字数0である場合
        if ( !str.isPresent() || str.get().length() == 0 ) {
            return str.orElse("Value is null or its length is 0.");
        }
        else {
            return str.get();
        }
    }

    public static void main(String[] args) {
        // nullableなString型フィールド
        Optional<String> str1 = Optional.ofNullable("ABC");
        Optional<String> str2 = Optional.ofNullable(null);

        System.out.println("str1: " + nullCheck(str1));
        System.out.println("str2: " + nullCheck(str2));
    }
}
```

```java:実行結果
str1: ABC
str2: Value is null or its length is 0.
```

***

# 用語集

|用語|内容|
|:--|:--|
|raw型|`ジェネリクス`の導入以前に用いられていた、<br>あらゆる`型`(=`Object型`)の代入を許容する`参照型`。<br>※非推奨|