今更Kotlinのジェネリクスについてしらべる
こちらは株式会社ガラパゴス(有志) Advent Calendar 2025の12日目の記事です。
はじめに...
ジェネリクスはしょっちゅう見かけるし使うんですが、いかんせんふわっと理解しているなぁ...と3年くらい思い続けていたので、これを機にジェネリクスマスターの階段をあがるぞということで調べてみました。
<T>、in、out なんかについてある程度理解できるはず...
基本の<T>について
class Box<T>(t: T) {
var value = t
}
こんなスニペットが公式にありますが、見ての通りジェネリクスを使うプレースホルダー的な型を定義できるため、呼び出し側で好きな型を渡せたりする便利なやつです。
一方で class だけにとどまらず関数にもつくのをよく見ます。(私は見るだけでちょっと嫌な気持ちになる)
fun <T> test(value: T): T {
return value
}
T が3連続していますが、別に絶対そうじゃないといけないわけではなく、戻り値を別の型にしたり、<T> を書いたにもかかわらず完全無視しても普通に動きます。
// T を宣言しているけど、どこでも使ってない例
fun <T> doNothing() {
// なんもしない
}
実際書くとわかりますが、<T>をつけたクラスやメソッドには呼び出し側で任意の型情報を渡せるよくらいに思って良さそうでした。
なので、以下のスニペットのような場合でも3つの型情報が渡ってくるんだな...くらいの理解でいいのかなと思います。
fun <T, E, GOD> test(value: String): String {
return value
}
ちなみにスニペットの通りここの名前はわりと自由がききます。GOD が許される(まあ神だしね)。
一定の慣例があるみたいで <E> あたりはコレクション要素としてよく使われるみたいです。あとは Map<K, V> なんかの Key-Valueなど....
ひとまずよく使われるのは<T>のためここら辺の慣例に当てはまらないものは<T>として問題ないと思います。
in / outについて
interface OutTest<out T> {
fun hogeOut(): T
}
interface InTest<in T> {
fun hogeIn(value: T)
}
かなりよく見るけど永遠に目をそらしていた奴らになります...。
とはいえ並べるととてもよくわかってざっくり言うと、以下のような制約をかけてくれるキーワードになります。
- out は「T を引数としては使えないようにする(= 主に"出す側"で使う)」
- in は「T を戻り値としては使えないようにする(= 主に"入れる側"で使う)」
例えば hogeOut に T を受ける引数を追加するとエラーが発生し、hogeIn の戻り値を T にするとこれもまたエラーが発生します。
また、あくまで T 型の扱いが出力/入力専用になるだけなので、hogeOut がなにも返さない関数でも問題ないです。
interface OutTest<out T> {
fun hogeOut1(): T // 正常!!
fun hogeOut2(value: T) // これはエラー
fun hogeOut3(): String // これも正常!!(T を返さなくても許される)
}
見方としては、「この型(インターフェイスやクラス)は T を出す側なのか、入れる側なのか」を決めるための宣言だと思うと少しスッキリしました。
最後に
ざっくりきになりポイントをまとめてみましたが、まだきっとジェネリクスの表層を撫でただけなんだろうなという気がします。
whereを最後まで無視してブログを終えようとしていますしね。
ので、私のようにふわっとジェネリクスに向き合っていた人が調べるきっかけにでもなるといいなと思います。
そんで、ここまできてもやはり具体で使うタイミングが想像できないですね.... (ベストタイミングを教えてください)
次はもう少し突っ込んだジェネリクスの話をかけたらなと思います。
Discussion