Javaに関する様々な情報をご紹介します。

1ジェネリクスを使用したクラス

Javaに関する様々な情報をご紹介します。

ジェネリクスを使用したクラス

このページではJava5.0から追加されたジェネリクスを使用してクラスを定義する方法について説明します。

ジェネリクスとは

プログラムを行う際、あらかじめ取り扱うデータ型を指定してプログラムを行います。ただ、場合によってはデータ型を指定せず、プログラムの振る舞いだけを定義しデータ型はその都度指定したい場合があります。何かを格納するクラスを作成する場合、格納する物ごとにクラスを作成するのではなく、汎用的なクラスを一つ作成し、格納する物をその都度指定できるクラスがあれば便利です。

ジェネリクスを使用すると、データ型そのものを変数として扱えるため、このようなクラスを作成することが可能です。

ジェネリクス

ジェネリクスの使用方法

クラスを宣言する際、クラス内で任意のデータ型に置き換わる型(型パラメータ)を指定します。

class クラス名<型パラメーター, ・・・> { クラス本体 }

ジェネリクスを使用して、汎用的な棚クラスを作成した例です。

Shelfクラス(ジェネリクス)

//クラス内で使用する型パラメーターとして、Tを宣言
public class Shelf<T> {
  //型パラメータを適用する箇所に変数のデータ型としてTを宣言
  private T value;

  //型パラメータを適用する箇所に変数のデータ型としてTを宣言
  public void setValue (T value) {
    this.value = value;
  }

  //型パラメータを適用する箇所に変数のデータ型としてTを宣言
  public T getValue() {
    return value;
  }
}

Mainクラス

class Main {
  public static void main(String[] args) {
    //インスタンスを生成する際、使用する型を指定。本ケースでは、Integer型を指定。
    Shelf<Integer> ins1 = new Shelf<Integer>();

    ins1.setValue(123);
    Integer i = ins1.getValue();
    System.out.println(i);
  }
}

クラス内でTを宣言した箇所がInteger型に置き換えられ、Integer型のデータが使用できます。インスタンス生成時に他の型を指定した場合は、その型がクラス内で使用できます。このように、ジェネリクスを使用すると、データ型を型パラメータで変数として扱い、データ型をその都度指定可能なクラスが作成できます。

ジェネリクスでは、型パラメーターを宣言する際に、慣例的にTを使用します。特別な理由がない限りTを使用するようにします。

ジェネリクスの利点

ジェネリクスの利点は、データ型が都度指定可能なクラスを作成することだけではありません。インスタンスの生成時に型を指定することで、意図していない型の利用を防ぎ、プログラムの誤作動を防止することができます。

データ型を指定しないクラスを作成するのであれば、データ型がObject型のクラスを作成すれば、Object型はすべてのクラスの上位クラスに当たるため、要件は満たします。しかし、Object型のクラスではすべてのデータ型を許容するため、使用するデータ型に制限をかけられずそれがプログラムの誤動作を招きます。

以下では、その例を説明します。

ShelfOクラス(データ型にObject型を指定)

public class ShelfO {
  //データ型としてObject型を指定
  private Object value;

  //データ型としてObject型を指定
  public void setValue (Object value) {
    this.value = value;
  }

  //データ型としてObject型を指定
  public Object getValue() {
    return value;
  }
}

Mainクラス

class Main {
  public static void main(String[] args) {
    ShelfO ins1 = new ShelfO();

    ins1.setValue(123);
    //返値がObject型であるためInteger型の変数iに代入するためにキャストが必要
    Integer i = (Integer)ins1.getValue();
    System.out.println(i);
  }
}

上記は、先ほどのジェネリクスを使用したプログラムを、データ型にObject型を指定して書き直したものです。Object型はすべてのデータ型を許容するため、利用する際、目的のデータ型(ここではInteger型)へキャストする必要があります。

Mainクラス

class Main {
  public static void main(String[] args) {
    ShelfO ins1 = new ShelfO();

    ins1.setValue("Hello");
    //String型の"Hello"は、Integer型にキャストできず、実行時エラー
    Integer i = (Integer)ins1.getValue();
    System.out.println(i);
  }
}

上記は、表示する値にString型の「Hello」が誤って指定されていた例です。この場合、プログラム上では誤った動作をするが、Object型はすべてのデータ型を許容するためコンパイル時にエラーになりません。誤りが分かっているにもかかわらずコンパイル時にエラーにならず、実行時にエラーになるのでは安定的なプログラムとは言えません。

先ほどのジェネリクスを利用した例では、使用する型は<Integer型>に制限しているためコンパイル時にエラーになります。このように、ジェネリクスでは任意のデータ型が利用できるだけでなく、そのデータ型を制御できる仕組みも持っています。

まとめるとジェネリクス使用したクラスには以下のような利点があります。

  • 使用するデータ型毎にクラスを作るのではなく、任意のデータ型が指定できる汎用的なクラスが作成できる。
  • 使用するデータ型を制御して安定的なプログラムを記述する仕組みを持っている。

1ジェネリクスを使用したクラス