ジェネリック型とは、型をパラメーターとして受け取る、ジェネリッククラスまたはジェネリックインターフェースのことです。これにより、コンパイラーがそのクラスやインターフェースの型安全性を強制できるようになります。
次のような、ジェネリックでないContainerインターフェースを考えてみましょう。
class Container {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Object型を受け取って返すので、プリミティブ型以外のどんな型でも扱えます。しかし、これには代償があります。あるコードがIntegerを期待してgetを呼び出し、別のコードがStringを追加しようとsetを呼び出すと、実行時例外が発生してしまうのです。
ジェネリッククラスとジェネリックインターフェースは、次のような形式です。
class ClassName<T1, T2, ..., Tn> { ... }
interface InterfaceName<T1, T2, ..., Tn> { ... }
クラス名やインターフェース名の後に続く、山括弧(<>)で囲まれた型パラメーターの部分には、型パラメーター(型変数とも呼ばれます)T1、T2、…、Tnを指定します。これらはクラスやインターフェースの本体の中で使うことができ、ジェネリックな型安全性を得られます。
こちらがContainerのジェネリック版です。
class Container<E> {
private E object;
public void set(E object) { this.object = object; }
public E get() { return object; }
}
作成するときは、どの型を保持するのかを宣言する必要があり、コンパイラーがその制約を強制します。
// empty <> can infer from context
Container<String> stringContainer = new Container<>();
// compiler knows this is a String, so it is allowed
stringContainer.set("Some string");
// no cast needed, compiler knows it is a String
String result = stringContainer.get();
// this causes a compiler error:
stringContainer.set(42);