- 2018/2/27
- https://connpass.com/event/78536/
Kotlinでジェネリクスを学ぼう
ジェネリクス概要
単純なコンテナを作る例
class Box(val value:Any)
- Anyはあらゆる型の頂点(Nullableを除く)
Anyであることが注目点
値を入れるのは簡単
val box: Box = Box("Hello")
- だけど取り出しは大変
- キャスト
- 実行時に型でエラーの可能性
何をしたいか
- あらゆる型に対応させたい(固定の方で定義したくない) VS キャストしたくない(Anyを使いたくない)
- ここでジェネリクス
ジェネリッククラスの定義
- 型パラメータが宣言されたクラス
- 仮の型
class Box<T>(val value: T)
ジェネリッククラスのインスタンス生成
- 値を入れる
val box: Box<String> = Box<String>("Hello")
- 取り出し時にキャストいらなくなる
不変・共変・反変
- variance
- 変位/分散
- 不変・共変・反変
クラスと型
- 必ずしも一致しない
- 1つのクラスに複数の方
- String
- String, String?
- String
- 1つのクラスに無数の型
- ジェネリクス
- (さっきの)Box
不変(invariant)
- サブタイプの関係が成り立たない
- デフォルトだとこれ
Box<Int>
にBox<Number>
を入れられない
共変(covariant)
- 型パラメータと同じサブタイピング関係が成り立つ
out
を使うBox<out Number>
にBox<Int>
を入れられる
型プロジェクション
- 射影(projection)
- RDBにおいてはカラムの選択
- 型のある側面だけに注目
- 逆に言うとある側面を隠す
class MutableBox<T>(var value: T) val box1: MutableBox<Int> = MutableBox<123> val box2: MutableBox<out Number> = box1 box2.value = 0.5 // コンパイルエラー
- setterが削除されているから
- 型プロジェクションにより隠された
反変(contravariant)
- 型パラメータと逆のサブタイピング関係が成り立つ
in
を使う
fn setDefault(box; MutableBox<in Int>) { box.value = 0 } val box: MutableBox<Number> = MutableBox<NaN> setDefault(box) println(box.value) // 0
- 値のセットだけできる
まとめ
- 不変
- 入出力
- 共変
- 出力のみ
- 反変
- 入力のみ
変位指定(宣言場所指定と型プロジェクション)
型プロジェクション
- 射影(projection)
- 使用場所変位指定
val box1: Box<Int> = MutableBox<123> val box2: Box<out Number> = box1
- setter隠れてるからout不要
宣言場所変位指定
class Box<out T>(val value: T)
ジェネリック制約
- 型引数として指定できる型に制約をつける
- 制約とは上限境界のこと
class NumberBox<out T: Number>(val value: T) { fn toInt(): NumberBox<Int> = NumberBox(value.toInt()) }
複数の上限境界
- whereを使う
- これがジェネリック関数
型消去とreified型
- コンパイルすると型が消える
- 型チェックはスタープロジェクションを使うと回避できる
具象型(reifiled type)パラメータ付き関数
- reifiledを使う
メモ
- ブラウザでkotlin動かせる