ジェネリクス:総称型とは

Androidアプリ開発で、非同期処理の実装にAsyncTaskクラスを使っていた。
(なおAndroidの非同期処理はほかにもいくつか実装方法があり、AsyncTaskを使う方法はもう古いようだ。)

まあAsyncTask自体の話はどうでもよくて、そこで初めてジェネリクス(総称型)の実践を経験した。
AsyncTaskの情報を探すと、

android.os.AsyncTask<Params, Progress, Result>

とか

private class MyTask extends AsyncTask<Void, Void, Void> { ... }

とか出てくる。<~>って何? なんでそんな場所に書けるの? みたいな感じだった。調べてみたらジェネリクスなるものらしい。

結局AsyncTaskはなんとか動いたが、いまだにジェネリクスがよく分かってない。
APIの調べものとかしててジェネリクスが出てくると軽く緊張してしまう。

ということで、少しでも理解を進めていきたいな、というメモ書き。

ジェネリクス

総称型ともいい、あらゆる型がまとまっているモノ(型)を示すそうだ。
Javaだとすべてのクラスの元はObjectクラスで、すなわちすべての型の元はObject型なので、
ジェネリクス≒Object型
と考えた。どうやらそんなに間違ってはいない模様。
ただ、Object型が万能すぎて逆に使いにくかったのを、使いやすくしたのがジェネリクスてことみたいだ。

慣例的に、ジェネリクスの書き方は大文字のようで、以下が頻出ぽい。
<T>・・・Type、つまり型を入れるという示唆。これだけでよくね?と思ったがそうでもないらしく、以下が登場。
<E>・・・Element、つまり要素を入れるという示唆。配列やらコレクションやらの要素のことだろう。
<K>・・・Key、これは(Mapとかの)Keyを入れるという示唆。
<V>・・・Value、これは値なので、戻り値とか(Mapとかの)Valueを入れるという示唆。
複数の時は<T1, T2>みたいにカンマ区切りで。前述の

android.os.AsyncTask<Params, Progress, Result>

とかがその例だろう。

Object型だと、何でも入れられる反面、何が入っているかパッと見わからないし意図しない型の混入が起こる。
ジェネリクスの利点は、使うときに型を限定できるので、上記のような問題を防げる、ということのようだ。
(このへん、理解と語彙が足りず、うまいこと言えない)


クラスで使うなら、

class HogeClass<T>{

    //フィールド
    private T t;

    //コンストラクタ
    public HogeClass(T t){
        this.t = t;
    }

    //ゲッタ
    public T getT(){
        return t;
    }
}

こう書くと、HogeClassのインスタンスは以下のようにして得られる。

HogeClass<String> hogeClassStr = new HogeClass<String>("こんちは");  //右辺のStringは省略可
System.out.println(hogeClassStr.getT());  // → こんちは

HogeClass<Double> hogeClassDbl = new HogeClass<Double>(3.14);  //右辺のDoubleは省略可
System.out.println(hogeClassDbl.getT());  // → 3.14

ふむふむ。乱暴にまとめる。

クラスの宣言の{}の前に<T>て書いとくと、インスタンス生成時に
クラス名<指定したい型> 変数名 = new クラス名<※指定したい型>(コンストラクタへの引数)
とできる。Java7以降では※は省略可能で、<>となり、これをダイヤモンド演算子と呼ぶそう。

んでTは指定された型として、クラス(インスタンス)内で利用できる。

このTは、メソッド宣言の仮引数とそれを利用するときの実引数の関係に似ていて、仮型引数・実型引数と呼ばれる。
ちょっと自信ないけど、「バインド」という説明を見かけたが、たぶんこのnewで指定した型がクラス(インスタンス)に適用されるさまを言っているのではないか。
文字通り、指定した型で「縛り付ける」的な?

宣言の<T>は仮型引数なので<A>でも<fuga>でもいいんだけど前述のとおり、型=TypeのTという慣例を重んじるのが無難だろう。
・・・あー、そういうことか、

public interface Map<K,V>

という表記の言わんとしていることが分かった気がする。「K」eyの型、「V」alueの型、って伝えたいのね。

Object型でやろうとすると、「どんな型も入っちゃうので自前で管理せねば」と気にする必要があり、また不適切なものを入れてもコンパイルは通ってしまう。
だがジェネリクスを使えば、指定した型以外を入れようとするとコンパイルでチェックしてくれる。
古いJavaではジェネリクスがなかったので、先人方はこういった、Object型ゆえの自前管理コストに苦しんでいた模様。


まずはクラスでの簡単な用法を通じて、ジェネリクスの理解がちょっと進んだかもしんない。
引き続き今度は、メソッドでの用法(引数と戻り値でのジェネリクスの利用)なども調べたいところだ。