0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlin のジェネリクス入門

Posted at

はじめに

Kotlin では ジェネリクス (Generics) を使うことで、
型に依存しない柔軟で安全なコード を書けます。
Java のジェネリクスを継承しつつ、Kotlin らしい表現も可能です。


1. ジェネリクスとは?

ジェネリクスとは 型をパラメータとして扱う仕組み です。
コレクションを例にするとわかりやすいです。

val numbers: List<Int> = listOf(1, 2, 3)
val names: List<String> = listOf("Alice", "Bob")

List<T>T がジェネリクス。
List<Int>List<String> のように型を指定することで 型安全 を保証します。


2. ジェネリッククラス

自作のクラスにもジェネリクスを使えます。

class Box<T>(val value: T)

val intBox = Box(10)
val stringBox = Box("Hello")

println(intBox.value)   // 10
println(stringBox.value) // Hello

Box<T> と宣言し、利用時に Box<Int>Box<String> を指定します。


3. ジェネリック関数

関数にも型パラメータを導入できます。

fun <T> identity(value: T): T {
    return value
}

println(identity(42))        // Int
println(identity("Kotlin"))  // String

<T> を関数の先頭に書きます。
呼び出し時に型を指定することも可能です。

val result = identity<String>("Hello")

4. 型境界 (型制約: Upper Bounds)

ジェネリクスに 「この型のサブクラスだけ許可」 という制約を付けられます。

fun <T : Number> sum(a: T, b: T): Double {
    return a.toDouble() + b.toDouble()
}

println(sum(10, 20))     // 30.0
println(sum(3.5, 2.2))   // 5.7
// println(sum("a", "b")) // コンパイルエラー

T : Number と書くと、TNumber かそのサブクラスである必要があります。


5. 共変 (out) と反変 (in)

Kotlin のジェネリクスでは 型の変性 (variance) を明示できます。

共変 (out)

「読み取り専用の型パラメータ」
Producer<out T>T を生成する(出す)だけ の場合に使います。

class Producer<out T>(private val value: T) {
    fun produce(): T = value
}

val producer: Producer<String> = Producer("Hello")
val anyProducer: Producer<Any> = producer  // 共変なので代入可能

Producer<String>Producer<Any> に代入できる。


反変 (in)

「書き込み専用の型パラメータ」
Consumer<in T>T を消費する(受け取る)だけ の場合に使います。

class Consumer<in T> {
    fun consume(value: T) {
        println("Consumed: $value")
    }
}

val consumer: Consumer<Number> = Consumer()
val intConsumer: Consumer<Int> = consumer  // 反変なので代入可能

intConsumer.consume(10) // Consumed: 10

Consumer<Number>Consumer<Int> に代入できる。


6. 型消去 (Type Erasure)

Kotlin(JVM)では実行時にはジェネリクスの型情報が消えます。

fun <T> printType(list: List<T>) {
    if (list is List<String>) { // 警告あり: 実行時には判断できない
        println("String list")
    }
}

実行時には List<String>List<Int> も単なる List になるため、型チェックはできません。


7. reified 型パラメータ(インライン関数)

インライン関数 (inline) と組み合わせると、実行時に型情報を保持できる ようになります。

inline fun <reified T> isType(value: Any): Boolean {
    return value is T
}

println(isType<String>("Hello")) // true
println(isType<Int>("Hello"))    // false

reified によって実行時に型チェック可能になります。


まとめ

  • ジェネリクスは「型をパラメータ化する仕組み」
  • クラス・関数に <T> を導入できる
  • 型制約 (<T : Number>) で上限を設定可能
  • 変性 (out / in) を明示することで安全な代入が可能
  • 実行時は型消去されるが、reified を使えば型を保持できる

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?