Kotlinのみに限らずGenerics関数を実装している言語はc#, c++, Java, TypeScriptがあるそうですが、私は知らなかったのでここにGerericsについて学んだことをまとめていきたいと思います。
Genericsは変数の型をあえて指定しないようにすることでプログラムの柔軟性を高めることができます。
例えばクラスのインスタンスを生成するときに、引数としてDoubleとintの両方の型が入力されることが想定される場合、わざわざInt型用のクラスとDouble型用のクラスを分けて書くのは無駄なように思われます。これを解決してくれるのがGenericsで型を変数のようにTなどと仮置きしてプログラムを書いていくことでInt型、Double型の両方に対応できるクラスを作ることができます。またこの時使用する型は自作の型も問題なく使用できます。
定義方法 :
class クラス名<T(*仮型変数名)> (val 引数名: T)
ただし、デフォルトでTはnullable。つまり
class クラス名<T(*仮型変数名): Any?> (val 引数名: T)
と同様なので、nullを許容したくなければ?をとればよいので
class クラス名<T(*仮型変数名): Any> (val 引数名: T)
or
class クラス名<T(*仮型変数名): 具体的な型名> (val 引数名: T)
と記述する
Genericsでは色んな型を引数としてとれることが分かったかと思います。ここで問題となるのがJava とKotlinの間にある引数の型の扱いに違いです。Javaは引数としてX型を定義し、実際に利用するときにX型の子クラスであるY型の値を代入することが可能で、これができることを「共変」といいます。しかしKotlinはこれが許されません。Kotlinの場合は引数としてX型を定義した場合、X型の子クラスであるY型の値を代入するとコンパイルエラーを吐きます。このことを「不変」と言います。つまり、JavaとKotlinの間には引数として使いたい型を継承した子クラスの型を許容するか否かという違いがあるのです。
しかし、多くの場面で共変にしたい場面があるのでKotlinでは共変可能にできるようにする仕組みが用意されています。それがout
とin
です。outとinの使い方は使う場所の外側で使うか内側で使うかによって変わります。ちょっとわかりにくいのでGenerics型を用いてそれぞれの例を書いていきます。
out : 使用場所の外で変数を利用するときに使う(返り値として使う感覚)。今回の例では作成するクラスはTをひとつの外部に見せるパラメータ(返り値)になっています。
class クラス名<out T: 型(親クラス)>(val 引数名: T){
//out指定したクラスの外で引数として使用する
}
fun 関数名(引数名: クラス名<親クラス>){
//引数には親クラスを指定する
//親クラスで指定することで多数の継承先の子クラスがこの関数を使用できる(メリット)
}
fun main(){
関数名(子クラスA) //関数の呼び出し outをつけない場合ここでエラーメッセージが出る
関数名(子クラスB) //関数の呼び出し outをつけない場合ここでエラーメッセージが出る
}
別例 : こちらではちゃんと返り値としてTが使用されていることがわかると思います。
interface Source {
fun nextT(): T
}
fun demo(strs: Source) {
val objects: Source = strs // This is OK, since T is an out-parameter
// ...
}
引用[3]
ただしoutは引数をval指定にしなければなりません。また、outで指定することを「共変指定」といいます。また、どんな型の変数がわからないときは*が使える<out Any?> = <*>としてつかえる(Star-projectionという)。
in : 使用場所の内側で変数を利用するときに使う(フィールドとして使う感覚)
interface クラス名<in T: 型(親クラス)>(val 引数名: T){
//in指定したクラスの内側で引数として使用する
fun 関数名(引数名: クラス名<親クラス>){
//引数には親クラスを指定する
//親クラスで指定することで多数の継承先の子クラスがこの関数を使用できる(メリット)
}
}
class クラス名2 : クラス名<子クラス>{
override fun 関数名(引数名: クラス名<親クラス>)
}
fun main(){
関数名(子クラスA) //関数の呼び出し
関数名(子クラスB) //関数の呼び出し
}
別例
class クラス名<in T>{
fun 関数名(引数名 :T){
//処理
}
}
fun main(){
var 変数A: クラス名<子クラス> = クラス名<親クラス>
//継承先である子クラスの引数に親クラスを入れることもできるみたいです。
変数.関数名(変数A) //関数の呼び出し
}
inで指定することを「不変指定」といいます。
outは戻り値として外側に渡され、inは引数として内側に渡されます。
[1]必要最低限に理解する、ジェネリクスと共変・反変 - UUUM攻殻機動隊(エンジニアブログ)
[2]kotlin基本構文-ジェネリッック型,ジェネリック関数,共変と反変,out予約語 | ikuty.com
[3]Kotlin文法 - データクラス, ジェネリクス - Qiita