【JavaGold】汎用とコレクション

# 汎用とコレクション

<table>
  <caption>プリミティブ型に対応したラッパークラス</caption>
  <thead>
    <tr>
      <th>プリミティブ型のデータ</th>
      <th>対応するラッパークラス</th>
    </tr>
  </thead>
  <tr>
    <td>プリミティブ型のデータ</td>
    <td>対応するラッパークラス</td>
  </tr>
  <tr>
    <td>byte</td>
    <td>java.lang.Byte</td>
  </tr>
  <tr>
    <td>short</td>
    <td>java.lang.Short</td>
  </tr>
  <tr>
    <td>int</td>
    <td>java.lang.Integer</td>
  </tr>
  <tr>
    <td>long</td>
    <td>java.lang.Long</td>
  </tr>
  <tr>
    <td>double</td>
    <td>java.lang.Double</td>
  </tr>
  <tr>
    <td>float</td>
    <td>java.lang.Float</td>
  </tr>
  <tr>
    <td>char</td>
    <td>java.lang.Character</td>
  </tr>
  <tr>
    <td>boolean</td>
    <td>java.lang.Boolean</td>
  </tr>
</table>

#### 型パラメータとは
ジェネリクスのデータ型を後で決めるために一時的に使用するパラメータ。

```java:実行例
class Box<T> {
    private T contents;

    public void setContents(T contents) {
        this.contents = contents;
    }

    public T getContents() {
        return contents;
    }
}
```

上記の「T」となっている部分が型パラメータである。この部分は、実行時にどの型を使うかを指定することで、そのインスタンスが持つフィールドやメソッドの型パラメータが置き換わる。
この時、ジェネリクスを使用して型を記述する。

:::note info
なお、型パラメータを受け取るクラスを扱うクラスに型パラメータを渡さなかった場合、Object型の型パラメータが渡されたものとして解釈される。
:::

型推論が使える箇所
* 変数への代入
* メソッドの戻り値
* メソッド呼び出しの引数

#### ジェネリクスの性質
ジェネリクスは非変性である。
* サブクラス型の代入を許す **「共変性」**
* サブクラス型の代入を許さない **「非変性」**
* さらにスーパークラス型の代入を許す **「反変性」**

```java:実装例
public class A {
    public void hello() {
        system.out.println("A");
    }
}

public class B extends A {
    @Override 
    public void hello() {
        system.out.println("B");
    }
}

public class C {
    public void hello() {
        system.out.println("C");
    }
}

public class Test<T extends A> {
    public void execute(T t) {
       t.hello();
    }
}

public class Sample {
    public static void main(String[] args) {
        Test<A> a = new Test<>();
        Test<B> b = new Test<>();
        // コンパイルエラー
        Test<C> c = new Test<>();

        a.execute(new A());
        a.execute(new B());
        b.execute(new B());
    }
}
```
制限付きの型パラメータとは、ジェネリクスを宣言するときにextendsを使って、型パラメータとして使用できるクラスを制限する仕組み。


#### 非境界ワイルドカード
?と表される。これは任意の型を表すワイルドカードである。非境界ワイルドカードは読み書き両方の操作に使用されるが、型の情報は制限される。

```java:実装例
public void printList(List<?> list) {
    for (Object element : list) {
        System.out.println(element);
    }
}
```

上記の例では、printList()メソッドは任意の型のリストを受け取り、その要素を表示する。要素の具体的な型に依存しない処理に使用される。

##### 特徴
* メソッドの戻り値の型はObject型になる。
* メソッドの引数にはnullリテラルしか渡せない。


#### 上限境界ワイルドカード
? extends Typeと表される。Typeは特定のクラスやインターフェースを指定し、どのクラスが型パラメータとして使用できるかを制限する。
読み取り専用として使用され、特定の型の範囲を制限するために使用される。

* ワイルドカードに制限を設けると、Numberクラスまたはそのサブクラス型が型パラメータとして渡されてくることが保証できるため、戻り値型をObject型ではなく任意の型にすることができるようになる。

* 上限境界ワイルドカードを使っているときは、メソッドの引数にnull以外を渡すことができない。

```java:実装例
import java.util.ArrayList; 
import java.util.List;

public class Sample {
    public static void main(String[] args) {
        List<? extends Number> list = new ArrayList<Integer>();
        list.add(10);
        list.add(20);
        list.add(30);

        for (Number n: list) {
            System.out.println(n);
        }
    }
}
```
```:実行結果
addメソッドの行でコンパイルエラー
```

```java:実装例
public void processList(List<? extends Number> list) {
    // list内の要素に対する処理
}
```

上記の例では、processList()メソッドはNumberクラスまたはそのサブクラスのリストを受け取ることができる。

```java:Aクラス
public class A {
    public void hello() {
        System.out.println("A");
    }
}
```

```java:Bクラス
public class B extends A {
    @Override
    public void hello() {
        System.out.println("B");
    }
}
```

```java:Cクラス
public class C extends B {
    @Override
    public void hello() {
        System.out.println("C");
    }
}
```

```java:Testクラス
public class Test<T> {
    T value;

    public Test(T value) {
        super();
        this.value = value;
    }
    public T getValue() {
        return value;
    }
    public T setValue(T value) {
        this.value = value;
    }
}
```

```java:Sampleクラス
public class Sample {
    public static void main(String[] args) {
        Test<? extends B> test = new Test<B>(new B());
        B b = test.getValue();
        b.hello();

        Test<? extends B> test2 = new Test<C>(new C());
        B b2 = test2.getValue();
        b2.hello();

        // コンパイルエラー
        // Test<? extends B> test3 = new Test<A>(new A());
        // test.setValue(new B());
    }
}
```

Testクラスは型パラメータとして、Aもしくはそのサブクラスを型しか受け取らない。上記のように型パラメータとしてC型を渡すとコンパイルエラーとなる。


#### 下限境界ワイルドカード
型パラメータとして受け取れる最も具体的な型（下限）を制限し、それ以上の型であれば扱うことができる。

下限境界ワイルドカードの表記は ? super Type である。ここで、Typeは特定のクラス名やインターフェース名が入る。

上記のような型パラメータを与えると、非変であるジェネリクスの制限を緩和し、Typeに指定したクラスもしくはより上位の型であれば型パラメータとして受け取ることができる。

```java:実装例
public void processElements(List<? super Integer> list) {
    // Integerクラスまたはそのスーパータイプのリストを処理する
    // 要素の追加も可能
    list.add(42);
    for (Object element : list) {
        // 要素の処理
    }
}
```

上記の例では、List<? super Integer> 型の引数を受け取る processElements メソッドがある。これは、Integer クラスまたはそのスーパータイプのリストを処理することができる。また、list に対して新しい Integer オブジェクトを追加することも可能である。

下限境界ワイルドカードは、特定のクラスのスーパータイプの柔軟な処理や、書き込み可能な操作を行う場合に有用である。

```java:Aクラス
public class A {
    public void hello() {
        System.out.println("A");
    }
}
```

```java:Bクラス
public class B extends A {
    @Override
    public void hello() {
        System.out.println("B");
    }
}
```

```java:Cクラス
public class C extends B {
    @Override
    public void hello() {
        System.out.println("C");
    }
}
```

```java:Testクラス
public class Test<T> {
    T value;

    public Test(T value) {
        super();
        this.value = value;
    }
    public T getValue() {
        return value;
    }
    public T setValue(T value) {
        this.value = value;
    }
}
```

```java:Sampleクラス
public class Sample {
    public static void main(String[] args) {
        Test<? super B> a = new Test<A>(new A());
        Test<? super B> b = new Test<B>(new B());

        // 型パラメータにはBもしくはより上位しか使えないのでコンパイルエラー
        // Test<? super B> c = new Test<C>(new C());

        // AはBのサブクラスではないのでコンパイルエラー
        // a.setValue(new A());
        a.setValue(new B());
        b.setValue(new C());

        // 取り出すときには何型が入っているかが不安定なためObject型になる
        Object objA = a.getValue();
        Object objB = b.getValue();

        // 任意の型で扱う場合にはキャスト式が必要(ただし安全でない)
        A aObj = (A) a.getVfalue();
        B bObj = (B) b.getVfalue();
    }
}
```


#### java.util.Queue
最初に入れたデータが最初に取り出される。

#### java.util.Deque
両端から要素を挿入・削除できるデータ構造を定義する。

#### java.util.Set
重複する要素を許容しない。

#### java.util.List
インデックスによって順序付けられたデータ構造の機能を定義するコレクション。

#### java.util.Map
一意なキーとひも付けられた値のペア要素を扱う。

```java:実装例
import.java.util.Map;

public class Test {
    public static void main(String[] args) {
        var a = Map.entry(1, "A");
        var b = Map.entry(2, "B");
        var c = Map.entry(3, "C");
        Map<Integer, String> map = Map.ofEntries(a, b, c);

        for(Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey()
                                + ":"
                                + entry.getValue());
        }
    }
}
```

```java:実装例
import.java.util.HashMap;
import.java.util.Map;

public class Test {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "A");
        map.put(2, "B");
        map.put(3, "C");

        map.entrySet()
            .stream()
            .forEach((Map.Entry<Integer, String> entry) -> {
                Integer key = entry.getKey();
                String val = entry.getValue();
                System.out.println(key + ":" + val);
            });
    }
}
```

```:実行結果
1:A
2:B
3:C
```


#### java.lang.Comparable
* 自分自身が比較対象よりも前に位置するように並び替えるのであれば、負の正数を戻す。
* 自分自身が比較対象よりも後ろに位置するように並べ替えるのであれば、正の整数を戻す。
* 比較対象と同値であり、並べ替える必要がないのであれば0を戻す。

```Product.java
public class Product {
    private int id;
    public Product(int id) {
        super();
        this.id = id;
    }
    public int getId() {
        return id;
    }
}
```

```java:実装例
Comparator<Product> c = (p1, p2) -> p1.getId() - p2.getId();
```

```Sample.java
public class Sample {
    private int id;
    private String name;
    public Sample(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}
```

```SampleComparator.java
import java.util.Comparator;

public class SampleComparator implements Comparator<Sample> {
    @Override
    public int compare(Sample s1, Sample s2) {
        if (s1.getId() < s2.getId()) {
            return 1;
        }
        if (s2.getId() < s1.getId()) {
            return -1;
        }
        return 0;
    }
}
```

```Main.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Sample[] samples = {
            new Sample(2, "B"),
            new Sample(3, "C"),
            new Sample(1, "A")
        };
        List<Sample> list = new ArrayList<Sample>(Arrays.asList(samples));
        list.sort(new SampleComparator());
        for (Sample s : list) {
            System.out.println(s.getName());
        }
    }
}
```

* ArrayListのsortメソッドにSampleComparatorを引数で渡すと、sortメソッド内の処理でcompareメソッドを呼び出してソート処理を行っています。

* sort メソッドは受け取った Comparator インスタンスの compare メソッドを呼び出してソートすることになっています。

### compareとcompareToの違い
#### compare
* パッケージはjava.util.Comparator
* ラムダ式やstream.sorted()で使う。
* 引数は2つ
```java:実装例
public class Main {
    public static void main(String[] args) {
        int num1 = 1;
        int num2 = 2;
        System.out.println(Integer.compare(num1, num2));
    }
}
```

#### compareTo
* パッケージはjava.lang.Comparable
* Arrays.sort(Object[]), Collections.sort(List)で使う。
* 引数は1つ
```java:実装例
public class Main {
    public static void main(String[] args) {
        String str1 = "a";
        String str2 = "b";
         
        System.out.println(str1.compareTo(str2));
    }
}
```