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

1はじめてのジェネリクス

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

はじめてのジェネリクス

このページではJava5.0から追加されたジェネリクスの概要、基本的な使用方法について説明します。

ジェネリクスとは

Javaでクラスを作成する際、似たような機能のクラスを複数作るケースがあります。例えば、本を格納する本棚クラスと、ワインを格納するワイン棚クラスを作りたいとします。このとき、格納する物ごとにクラスを作るのではなく、汎用的なクラスを一つ作り、その中に様々な物が格納できるクラスが作れれば便利です。ジェネリクスは、このように扱うデータ型は様々であるが機能は同一であるといったクラスを作り仕組みです。

ジェネリクス

ジェネリクスの使用方法

ジェネリクスは、クラスを宣言する際、クラス内で使用する汎用的な型を表す型パラメーターを指定します。

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

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

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

//クラス内で使用する型パラメーターとして、Tを宣言
public class Shelf<T> {
  //汎用的な型を適用する箇所にTを宣言
  private T o;
  //汎用的な型を適用する箇所にTを宣言
  public Shelf(T o) {
    this.o = o;
  }
  //汎用的な型を適用する箇所にTを宣言
  public T out() {
    return o;
  }
}

Mainクラス

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

クラス内でTを宣言した箇所がInteger型に置き換えられ、Integer型のデータが使用できます。インスタンス生成時に他の型を指定した場合は、その型がクラス内で使用できます。このように、インスタンスを生成する際に型を指定することで、様々な型が使用できる汎用的なクラスを作成できます。

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

ジェネリクスの利点

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

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

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

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

public class ShelfO {
  //データ型としてObject型を指定
  private Object o;
  //データ型としてObject型を指定
  public ShelfO(Object o) {
    this.o = o;
   }
  //データ型としてObject型を指定
  public Object out() {
    return o;
  }
}

MainOクラス

class MainO {
  public static void main(String[] args) {
    ShelfO ins1 = new ShelfO(123);
    //返値がObject型であるためInteger型の変数iに代入するためにキャストが必要
    Integer i = (Integer)ins1.out();
    System.out.println(i);
  }
}

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

MainOクラス

class MainO {
  public static void main(String[] args) {
    ShelfO ins1 = new ShelfO("Hello");
    //String型の"Hello"は、Integer型にキャストできず、実行時エラー
    Integer i = (Integer)ins1.out();
    System.out.println(i);
  }
}

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

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

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

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

1はじめてのジェネリクス