【JavaGold勉強記#14】ジェネリクス編

JavaGold資格取得に向けた勉強記第14回は「ジェネリクス」についてです。

ここではJavaGold資格取得に向けて勉強している駆け出しプログラマーの私がアウトプットの意味も込めて、「ジェネリクス」について紹介させていただきます。

JavaGold資格取得に向けて勉強されている方や、プログラマー初心者の方にとって参考になれば幸いです!

ジェネリクス

ジェネリクスは、Javaにおいて異なる型のオブジェクトを扱う際に、型安全性を確保するための仕組みです。ジェネリクスを使用することで、コードの再利用性が向上し、型に関するコンパイル時のエラーが減少します。

Java SE 7では、変数宣言時の型パラメータを使って、コンストラクタ使用時の方パラメータを推論する「型推論」という機能が盛り込まれました。型推論は、ダイヤモンド演算子< >」を記述するだけで実現できます。

List<String> list = new ArrayList<>();

ジェネリクスの型推論は、変数への代入など型が明確でなければ使えません。型推論が使えるのは以下のような箇所です。

  • 変数への代入
  • メソッドの戻り値
  • メソッドの呼び出しの引数
import java.util.ArrayList;
import java.util.List;

public class javaGold {
    public static void main(String[] args) {
        // 変数への代入
        List<String> list = new ArrayList<>();
        // メソッド呼び出しの引数
        execute(new ArrayList<>());
    }

    private static List<String> test() {
        // メソッドの戻り値
        return new ArrayList<>();
    }

    private static void execute(List<String> list) {
        // do something
    }
}

制約つきの型パラメータ

制約付きの型パラメータとは、ジェネリクスを宣言するときにextendsを使って、型パラメータとして使用できるクラスを制限する仕組みです。

例えば、<T extends A>のように制約付きの型パラメータを使うことで、型パラメータとして使用できるクラスをAクラスもしくはAクラスのサブクラスに制限することができ、互換性が保証されます。

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

変性

変性(Variance)は、ジェネリクスにおいて型パラメータがサブタイプ関係をどのように保つかに関する概念です。

Javaにおいては、主に共変性(covariance)、反変性(contravariance)、非変性(invariance)の3つの変性があります。これらはワイルドカードとともに使用され、ジェネリック型をより柔軟に取り扱うことを可能にします。

共変性

共変性は、サブタイプ関係が保たれる性質です。言い換えれば、型Aが型Bのサブタイプであれば、ジェネリック型SomeType<A>SomeType<B>のサブタイプとなります。

class Fruit { /* ... */ }
class Apple extends Fruit { /* ... }

List<? extends Fruit> fruits = new ArrayList<Apple>(); // 共変性

この例では、AppleFruitのサブタイプであるため、List<Apple>List<? extends Fruit>のサブタイプとなります。

反変性

反変性は、型の逆のサブタイプ関係が保たれる性質です。言い換えれば、型Aが型Bのサブタイプであれば、ジェネリック型SomeType<B>SomeType<A>のサブタイプとなります。

class Box<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }
}

Box<? super Apple> box = new Box<Fruit>(); // 反変性

この例では、Box<? super Apple>Box<Fruit>のサブタイプとなります。superワイルドカードは反変性を表します。

非変性

非変性は、型のサブタイプ関係を持たない性質です。すなわち、型Aが型Bのサブタイプであっても、ジェネリック型SomeType<A>SomeType<B>は直接的なサブタイプ関係にはありません。

List<Fruit> fruits = new ArrayList<Apple>(); // 非変性

この例では、List<Fruit>List<Apple>は直接的なサブタイプ関係にありません。型安全性が保たれる一方で、柔軟性が制限される特性を持っています。

ワイルドカード

ワイルドカードは、Javaにおいてジェネリクスを使用する際に、型の柔軟性を提供するための機能です。ワイルドカードは主にメソッドの引数や戻り値、またはクラスやインターフェースの型パラメータで使用されます。

ワイルドカードには主に3つの種類があります。

非境界ワイルドカード「?」

?は未知の型を表します。具体的な型が不確定であり、ワイルドカードを使用した部分には任意の型が指定できます。

List<?> list = new ArrayList<Integer>(); // 非境界ワイルドカード

非境界ワイルドカードには次の特徴があります。

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

上限境界ワイルドカード「? extends T」

? extends Tは、型TまたはTのサブタイプを表します。主に読み取り専用のコンテナに使用されます。

List<? extends Number> numbers = new ArrayList<Integer>(); // 上限境界ワイルドカード

上限境界ワイルドカードを使用することで、非境界ワイルドカードの特徴である「メソッドの戻り値型はObject型になる」を「任意の型にする」ことができるようになります。

下限境界ワイルドカード「? super T」

? super Tは、型TまたはTのスーパータイプを表します。主に書き込み可能なコンテナに使用されます。

List<? super Integer> integerList = new ArrayList<Number>(); // 下限境界ワイルドカード

下限境界ワイルドカードを使用することで、非境界ワイルドカードの特徴である「メソッドの引数にはnullリテラルしか渡せない」を「任意の型にする」ことができるようになります。

メソッド内の目的が、戻り値を戻すことを目的とした「Producer(提供者)」であればextendsを、引数を受け取って利用することを目的とした「Consumer(消費者)」であればsuperを使います。この原則はProducer-Extends and Consumer-Superを略して「PRCS」とも呼ばれます。

最後に

ジェネリクスは、Javaプログラミングにおいて型安全性とコードの再利用性を向上させる強力な機能です。型パラメータを使用して異なる型に対して一般的なコードを適用することで、プログラムの柔軟性が向上し、コードの品質が向上します。ワイルドカードを組み合わせることで、より柔軟で拡張可能なジェネリックなコードを書くことができます。