「初学者向け「Kotlinでジェネリクスを学ぼう」」に参加してきました

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?
  • 1つのクラスに無数の型

不変(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())
}

複数の上限境界

型消去とreified型

  • コンパイルすると型が消える
  • 型チェックはスタープロジェクションを使うと回避できる

具象型(reifiled type)パラメータ付き関数

  • reifiledを使う

メモ