Java Generics Hell メソッドスコープのジェネリクス

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

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

前回、構文の話をしていたが、そのなかでメソッドスコープのジェネリクスをいったんおいていた。
今回はそのあたりを取り上げる。

ジェネリクスとスコープ

前回のおさらいとして、Javaジェネリクスには2つのスコープがある話をしていた。

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

まずジェネリクスのそもそも論になるが、ある処理の塊に型変数という「型」を表す変数を導入し、処理の塊をいろんな型で扱えるようにしよう、ということになる。ここで「処理の塊」としてJavaジェネリクスでは「メソッド」か「インスタンス」か、ということになる。

メソッドスコープの型変数は

で宣言することができ、その内部を範囲として型変数を用いることが出来る。インスタンス・メソッドや、コンストラクタの場合は、クラスのインスタンスをスコープとする型変数と、そのメソッドだけを有効範囲とするメソッドスコープの型変数が混在することがある。内部クラスや無名クラス、ラムダ式などを考慮すると型変数の有効範囲というのはもう少しややこしいのだが、まずはおいておこう。

過去のセッション資料で取り上げた図を挙げておこう。メソッドスコープの型変数は、メソッドのIN / OUTにおいてどことどこが同じ型なのかを表すことになる。


構文

簡単に構文を説明しておこう。標準APIから抜粋する

public static <T> Set<T> singleton(T o) {
  // 略
}

https://docs.oracle.com/javase/jp/9/docs/api/java/util/Collections.html#singleton-T-

public static の後の<T>が型変数宣言である。次のSet<T>は戻り値の型、singletonはメソッド名、その後の()は引数の宣言である。要するに通常のメソッド宣言の戻り値の手前の部分に<T>といったように山括弧で型変数を宣言する。複数の型変数の場合はカンマで区切る。

使用する場合は、通常はバインド型が推論されるのでそのまま呼び出せば済む。

Set<String> set = Collections.singleton("hoge");

型変数にバインドする型を明示する場合はメソッドの手前に山括弧で指定する。

Set<String> set = Collections.<String>singleton("hoge");

インスタンスメソッドの呼び出しの場合、メソッド名だけで呼び出そうとするとバインドを明示できない。この場合はthis.を補う必要がある。staticメソッドで自クラス内のメソッドを呼ぶ場合も似たような感じで、クラス名.を補わなければならない。このあたりは構文上ちょっと苦しい。

しかし、コンストラクタでのメソッドスコープ型変数のバインドの場合、newでのバインドとかぶるので無理やり位置を変えて対処したのだろう。まぁコンストラクタのメソッドスコープの型変数というのは構文上可能だが使い道はまずない。

Hoge<Integer> hoge = new <String>Hoge<Integer>();

メソッドスコープの型変数の例

Javaの標準APIからメソッドスコープの型変数の例を挙げよう。java.util.Collectionsクラスからとなる。このクラスはListなどのコレクションフレームワークと呼ばれるクラス群に対しての便利なstaticメソッド集という感じである。

さて、先程「メソッドのIN / OUTにおいてどことどこが同じ型なのか」といったが、実例をみてみよう。

public static <T> Set<T> singleton(T o)

https://docs.oracle.com/javase/jp/9/docs/api/java/util/Collections.html#singleton-T-

引数がT型で、戻り値がSet<T>型となっている。引数と戻り値に型変数で対応付けがされているのが分かるだろう。

public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)

https://docs.oracle.com/javase/jp/9/docs/api/java/util/Collections.html#replaceAll-java.util.List-T-T-

この例では戻り値はboolean型で型変数は関係ないが、複数の引数があり、それらがList<T>型、T型、T型であるという関連性であることがわかる。

public static void shuffle(List<?> list)

https://docs.oracle.com/javase/jp/9/docs/api/java/util/Collections.html#shuffle-java.util.List-

対して、このshuffleというメソッドでは型変数は用いられておらずワイルドカードでList<?>とされている。ワイルドカードについては別稿で詳しく挙げるが、ざっくり言えば何型でもいいというわけだ。それは、shuffleの引数がただひとつであり、また戻り値もvoidであって、メソッドの引数や戻り値間の型の関連性をチェックする必要性がないからだ。

このように、引数間や戻り値の間で、こことここの型が同じ、だが、その型はべつになんでも良い、という場合にメソッドスコープのジェネリクスが用いられる。

まとめ

  • メソッドスコープの型変数は以下で宣言して用いることが出来る
  • 引数同士、あるいは引数と戻り値の型の関連性を表すことができる

ジェネリクスの導入としてメソッドスコープの型変数の使われ方をみてみた。これを踏まえて次回はインスタンススコープの場合の話を取り上げたい。