Java Generics Hell - ジェネリクスの構文

Java Generics Hell アドベントカレンダー 6日目。

読者の推奨スキルとしてはOCJP Silverぐらいを想定している。

ここまで話の簡単のためにいろいろ端折って書いてきているのだが、徐々に詳細を書かねばなるまい。同時に取り漏らしも回収して行かなければならない。

スコープ

Javaジェネリクスの型変数には2種類のスコープがある。

  • メソッドの範囲で有効な型変数
  • クラスのインスタンスの範囲で有効な型変数

List<String>といった例でバインドされているのは後者のクラスのインスタンスを範囲としたもの。よく目にするのがこちらだろう。

前者のメソッドの範囲の型変数は入門書ではあまり取り上げられていないかもしれない。しかし、話としてはメソッドスコープの方が単純なのできっちりやるならメソッドスコープから導入したほうがわかりやすいのではないかと筆者は考える。

また、「インスタンスの範囲」と言っているのは、型変数への型のバインドがインスタンス単位で行われるので、staticメソッドではクラススコープの型変数が参照できないことを指している。例えばList<String>とList<Integer>といったものが宣言できるわけだが、Listのコード中で型変数EがこのStringやIntegerとして扱われるわけだ。ではstaticメソッドでは?インスタンス単位でEをStringとして扱います、Integerとして扱います、とやっているわけだから、staticメソッドでEを用いることは出来ない。

さて、そこまで話を振っておきつつ、メソッドスコープの話は先送りで今回はこれ以上つっこまない。

3種の山括弧

Javaジェネリクスの構文で重要なのは<>の山括弧には主に3種類あるということだ。<>を見たら漠然と「ジェネリクス」と考えているかもしれない。しかし、ちゃんとマスターしたければ、まず構文上の違いを理解しなくてはならない。

型変数の宣言

まずは型変数の宣言。型変数を用いたジェネリックなクラスを自作することがなくとも、ソースコードから宣言をみる必要性が生じることもある。理解しておこう。

public class Hoge<T> {
}

クラスを宣言する際にクラス名の後ろで山括弧に型変数名を宣言する。複数の型変数を宣言する場合はカンマで区切って並べる。

public class Piyo<T1, T2> {
}

型変数名は慣習としてTypeの頭文字Tとすることが多い。構文上はclass名などと同じで事由に識別子を設定できるが、一般的なclass名の命名規約と同等のキャメルケースとかにすると紛らわしい。ジェネリクスで抽象化する時点で扱いが抽象的になるので、Typeの頭文字TとかElementの頭文字Eとかになることが多いが、より具体的に名付けたければRESULTなど英語大文字が一般的か。言語仕様上は日本語などでも構わない。

パラメタライズドタイプ

次はパラメタライズドタイプ。これは前回も出てきたが、変数宣言の際の型で用いる。

// 変数の宣言
List<String> stringList;

また、メソッド引数の型などでも用いられる。

public void hoge(List<String> list) {
  // ...
}

バインド

そしてバインド。バインドにはいくつかあるが、本稿では基礎的なnewの際のバインドだけを挙げておこう。

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

この場合、左辺のList<String>はパラメタライズドタイプで右辺のnew ArrayList<String>();の<String>がバインド部分である。

この際の山括弧はListの型変数に対してString型をあてはめる(バインドする)という意味合いである。ListのEはStringですよ、というわけだ。
複数の型変数を持つクラスの場合はカンマで区切って複数の型を指定する。

Map<String, Integer> map = new HashMap<String, Integer>();

まとめ

このように、Javaジェネリクスの山括弧は主に3種類あり、役割が違う。また、ここではもっとも単純な例のみ挙げたが、実際には変性の指定やら、境界の指定やらあってもっといろんな書き方がある。また、メソッドスコープの場合も書き方が異なる。
しかしまずは構文を意識してソースを眺めることが大事だ。漠然と眺めていては理解がすすまない。まずは意識して構文をを見るようにしてほしい。

次回はメソッドスコープのジェネリクスについて触れたい。