ジェネリクス

ジェネリクスにより、コードはより安全で柔軟になります。このレッスンでは、Javaジェネリクスの使用方法を学びます。

ジェネリクスとは

ジェネリクスは、クラスやメソッドが複数の型で動作できるようにするパラメータ化された型です。

ジェネリクスの利点

利点 説明
型安全性 コンパイル時の型チェック
コード再利用 1つのコードが複数の型で動作
キャスト排除 手動の型変換が不要

ジェネリッククラス

構文

JAVA
public class クラス名<T> {
    // Tを型として使用
}

例:ジェネリッククラス

JAVA
public class Box<T> {
    private T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }
    
    public static void main(String[] args) {
        // Integerを格納
        Box<Integer> intBox = new Box<>();
        intBox.set(100);
        int num = intBox.get();  // キャスト不要
        
        // Stringを格納
        Box<String> strBox = new Box<>();
        strBox.set("Hello");
        String str = strBox.get();
        
        System.out.println("Integer Box: " + num);
        System.out.println("String Box: " + str);
    }
}
▶ 試してみよう

複数のジェネリックパラメータ

JAVA
public class Pair<K, V> {
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
    
    @Override
    public String toString() {
        return key + "=" + value;
    }
    
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("Alice", 95);
        System.out.println(pair);  // Alice=95
    }
}

ジェネリックメソッド

構文

JAVA
public static <T> メソッド名(パラメータ) {
    // Tを使用
}

例:ジェネリックメソッド

JAVA
public class GenericMethod {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }
    
    public static <T> T getFirst(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[0];
    }
    
    public static <T extends Comparable<T>> T getMax(T[] array) {
        T max = array[0];
        for (T element : array) {
            if (element.compareTo(max) > 0) {
                max = element;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        Integer[] nums = {1, 2, 3, 4, 5};
        String[] names = {"Alice", "Bob", "Charlie"};
        
        printArray(nums);    // 1 2 3 4 5
        printArray(names);   // Alice Bob Charlie
        
        System.out.println("最初の数: " + getFirst(nums));  // 1
        System.out.println("最初の名前: " + getFirst(names));  // Alice
        
        System.out.println("最大値: " + getMax(nums));  // 5
        System.out.println("最大の名前: " + getMax(names));  // Charlie
    }
}
▶ 試してみよう

ジェネリックインターフェース

ジェネリックインターフェースの定義

JAVA
public interface Repository<T> {
    void add(T item);
    T get(int index);
    List<T> getAll();
}

ジェネリックインターフェースの実装

JAVA
public class ArrayListRepository<T> implements Repository<T> {
    private List<T> list = new ArrayList<>();
    
    @Override
    public void add(T item) {
        list.add(item);
    }
    
    @Override
    public T get(int index) {
        return list.get(index);
    }
    
    @Override
    public List<T> getAll() {
        return new ArrayList<>(list);
    }
    
    public static void main(String[] args) {
        Repository<String> repo = new ArrayListRepository<>();
        repo.add("Alice");
        repo.add("Bob");
        System.out.println(repo.getAll());  // [Alice, Bob]
    }
}

ジェネリックワイルドカード

ワイルドカードなし ?

JAVA
public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

上限ワイルドカード ? extends T

JAVA
// Numberとそのサブクラスのみ受け入れ
public static double sum(List<? extends Number> list) {
    double total = 0;
    for (Number num : list) {
        total += num.doubleValue();
    }
    return total;
}

下限ワイルドカード ? super T

JAVA
// Integerとそのスーパークラスのみ受け入れ
public static void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

例:ワイルドカード

JAVA
import java.util.ArrayList;
import java.util.List;

public class WildcardDemo {
    // ワイルドカードなし
    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.print(item + " ");
        }
        System.out.println();
    }
    
    // 上限ワイルドカード
    public static double sum(List<? extends Number> list) {
        double total = 0;
        for (Number num : list) {
            total += num.doubleValue();
        }
        return total;
    }
    
    public static void main(String[] args) {
        List<Integer> ints = new ArrayList<>();
        ints.add(1);
        ints.add(2);
        ints.add(3);
        
        List<String> strs = new ArrayList<>();
        strs.add("Alice");
        strs.add("Bob");
        
        printList(ints);  // 1 2 3
        printList(strs);  // Alice Bob
        
        System.out.println("合計: " + sum(ints));  // 6.0
    }
}
▶ 試してみよう

型消去

Javaジェネリクスは型消去によって実装されています。ジェネリック情報はコンパイル後に消去されます。

JAVA
// コンパイル前
List<String> list = new ArrayList<>();
list.add("Hello");

// コンパイル後(型消去)
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);  // 自動キャスト
⚠️ 注意: 実行時にジェネリック型情報を取得することはできません。List<String>List<Integer>は実行時には同じ型です。

ジェネリック制約

例:境界付きジェネリクス

JAVA
// TはComparableを実装する必要がある
public static <T extends Comparable<T>> T min(T a, T b) {
    return a.compareTo(b) <= 0 ? a : b;
}

// TはNumberのサブクラスである必要がある
public static <T extends Number> double doubleValue(T num) {
    return num.doubleValue();
}
▶ 試してみよう

一般的なジェネリック名

名前 意味
T Type(型)
E Element(要素)
K Key(キー)
V Value(値)
N Number(数値)

❓ よくある質問

Q なぜジェネリクスを使用するのですか?
A 型安全性、コード再利用、キャスト排除のためです。
Q ジェネリクスはプリミティブ型をサポートしていますか?
A いいえ、ラッパークラスを使用してください。例えば、List<int>List<Integer>にする必要があります。
Q <?><Object>の違いは何ですか?
A <?>は不明な型で、読み取り専用です。<Object>はObject型で、読み書き可能です。

📖 まとめ

📝 演習

  1. ジェネリックコンテナ: ジェネリックなStackクラスを実装
  2. ジェネリックメソッド: 配列をListに変換するジェネリックメソッドを実装
  3. 境界付きジェネリクス: 2つのオブジェクトを比較するメソッドを実装

次のレッスン

次のレッスンでは、実践:コレクション応用を学びます — コレクション知識を適用します。

100%