Kotlin のジェネリクスは、クラスや関数に「型の引数」を持たせて、1つのコードをいろいろな型で安全に使い回す仕組みです。 List<String> の String の部分が型引数で、取り違えはコンパイル時に見つかります。つまずきやすいのは、out・in・*(変位)と、実行時に型引数が消える「型消去」の2か所です。

この記事では、不変・共変 out・反変 in・スター投影を「渡せる型/取り出せる型/代入できる向き」の早見表と図で整理し、List と MutableList で代入が通る例・通らない例をエラー文つきで示します。is T が書けないエラーと inline + reified での解決、where による複数の型制約、T と T : Any の null 許容の差、Java のワイルドカードとの対応まで、すべて実行して確かめた結果を載せています。

確認環境:Ubuntu 24.04 / Kotlin 2.4.20(kotlinc-jvm)/ OpenJDK 21.0.10。掲載した出力とエラー文は、すべてこの環境でコンパイル・実行したものです。

変位の早見表:不変・out・in・スター投影

Apple が Fruit の子クラスであるとして、4つの書き方を比べます。

書き方 呼び名 T を渡せるか(引数) T を取り出せるか(戻り値) 代入できる向き 標準ライブラリの例
Box<T> 不変 できる できる 同じ型引数どうしだけ MutableList<E>、Array<T>
Box<out T> 共変 できない できる Box<Apple> を Box<Fruit> へ(子から親へ) List<out E>
Box<in T> 反変 できる Any? としてだけ Box<Fruit> を Box<Apple> へ(親から子へ) Comparable<in T>
Box<*> スター投影 できない Any?(上限の型)として どの型引数の Box も代入できる List<*> で「中身を問わないリスト」

覚え方は「取り出すだけなら out、渡すだけなら in」です。 公式ドキュメントでも Consumer in, Producer out という言い回しで紹介されています。

この記事で扱うほかの書き方の一覧です。

やりたいこと 書き方
ジェネリックなクラス class Box<T>(val value: T)
ジェネリックな関数 fun <T> first(list: List<T>): T
型の上限を1つ付ける fun <T : Comparable<T>> max(a: T, b: T): T
型の制約を複数付ける fun <T> f(x: T) where T : Named, T : Comparable<T>
null を入れさせない fun <T : Any> f(x: T)
実行時に T を調べる inline fun <reified T> f(x: Any) = x is T
使う場所でだけ out / in を付ける fun copy(from: MutableList<out Number>, to: MutableList<in Int>)
中身の型を問わない fun size(list: List<*>): Int

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlinジェネリクスの使い方 in・out・reified・whereを図解」

ジェネリクスの基本:クラスと関数に型引数を付ける

型引数は、クラスでは名前の後ろ、関数では fun の直後に山かっこで書きます。

class Box<T>(val value: T) {
    fun <R> map(transform: (T) -> R): Box<R> = Box(transform(value))
    override fun toString() = "Box($value)"
}

fun <T> firstOrDefault(list: List<T>, default: T): T = if (list.isEmpty()) default else list[0]

fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T): T {
    var m = a
    if (b > m) m = b
    if (c > m) m = c
    return m
}

fun main() {
    val b = Box(21)                         // Box<Int> と推論される
    println(b.map { it * 2 })
    println(b.map { "値は$it" })
    println(firstOrDefault(emptyList(), "なし"))
    println(firstOrDefault<Int>(listOf(7, 8), 0))
    println(maxOf3(3, 9, 5))
    println(maxOf3("pear", "apple", "kiwi"))
}

実行コマンドと出力です。日本語を正しく表示するため、java に -Dstdout.encoding=UTF-8 を付けています。

kotlinc basic.kt -include-runtime -d basic.jar
java -Dstdout.encoding=UTF-8 -jar basic.jar
Box(42)
Box(値は21)
なし
7
9
pear
  • 型引数はたいてい推論される:Box(21) と書けば Box<Int> になります。明示するときは firstOrDefault<Int>(...) のように関数名の直後に書きます。
  • T : Comparable<T> は上限(upper bound):T を「比べられる型」に絞ることで、関数の中で b > m と書けるようになります。
  • クラスの T とメソッドの R は別の型引数:map は Box の中身を変換して、別の型の Box を返しています。

out(共変):List は代入できて MutableList はできない

Apple は Fruit の子クラスですが、だからといって MutableList<Apple> が MutableList<Fruit> の子になるわけではありません。まず、通る例と通らない例を並べます。

fun main() {
    val strings: List<String> = listOf("a", "b")
    val anys: List<Any> = strings              // OK

    val mStrings: MutableList<String> = mutableListOf("a", "b")
    val mAnys: MutableList<Any> = mStrings     // エラー
}
e1.kt:6:33: error: initializer type mismatch: expected 'MutableList<Any>', actual 'MutableList<String>'.
    val mAnys: MutableList<Any> = mStrings     // エラー
                                ^

List は通り、MutableList は通りません。 理由は、もし MutableList で代入が通ると、mAnys.add(123) で String のリストに数値を入れられてしまうからです。List は読み取り専用で「取り出すだけ」なので、String のリストを Any のリストとして見ても困ることがありません。

flowchart LR
    subgraph OUT["out(共変): 取り出すだけ"]
        A1["Apple の List"] -->|"代入できる"| F1["Fruit の List"]
    end
    subgraph INV["不変: 渡す・取り出す両方"]
        A2["Apple の MutableList"] -.->|"代入できない"| F2["Fruit の MutableList"]
    end
    subgraph IN["in(反変): 渡すだけ"]
        F3["Fruit の Sink"] -->|"代入できる"| A3["Apple の Sink"]
    end

この違いは宣言に書かれています。Kotlin の List は interface List<out E>、MutableList は interface MutableList<E> です。配列の Array<T> も不変なので、同じエラーになります。

v2.kt:3:28: error: initializer type mismatch: expected 'Array<Any>', actual 'Array<String>'.
    val anyArr: Array<Any> = arr
                           ^

in(反変):受け取る側は親の型で代用できる

in は out の逆向きです。「Fruit なら何でも受け取れるもの」は「Apple を受け取るもの」として使えます。自分で out と in を付けたクラスを作って確かめます。

open class Fruit(val name: String) { override fun toString() = name }
class Apple : Fruit("りんご")
class Orange : Fruit("みかん")

interface Source<out T> { fun next(): T }           // 共変: T を返すだけ
interface Sink<in T> { fun put(item: T) }           // 反変: T を受け取るだけ

class AppleSource : Source<Apple> { override fun next() = Apple() }
class FruitSink : Sink<Fruit> { override fun put(item: Fruit) = println("受け取り: $item") }

fun printAll(fruits: List<Fruit>) = println(fruits)

fun main() {
    val apples: List<Apple> = listOf(Apple(), Apple())
    val fruits: List<Fruit> = apples                 // OK: List<out E> は共変
    printAll(apples)
    println(fruits.size)

    val src: Source<Fruit> = AppleSource()           // OK: Source<Apple> → Source<Fruit>
    println(src.next())

    val sink: Sink<Apple> = FruitSink()              // OK: Sink<Fruit> → Sink<Apple>
    sink.put(Apple())

    val byName: Comparator<Fruit> = compareBy { it.name }
    val sorted = listOf(Apple(), Apple()).sortedWith(byName)   // Comparator<in T>
    println(sorted)
}
[りんご, りんご]
2
りんご
受け取り: りんご
[りんご, りんご]

最後の sortedWith が in の身近な例です。Fruit を比べられる Comparator は、Apple のリストの並べ替えにもそのまま使えます。 Apple は Fruit でもあるので、Fruit 用の比較は Apple にも当てはまるからです。

out・in を付けたクラスで書けないこと

out を付けた型引数は戻り値の位置にだけ、in を付けた型引数は引数の位置にだけ書けます。逆の位置に書くと、次のエラーで知らせてくれます。

class Box<out T>(private var value: T) {
    fun get(): T = value
    fun set(v: T) { value = v }
}

interface Sink<in T> {
    fun put(item: T)
    fun last(): T
}
e2.kt:3:16: error: type parameter 'T' is declared as 'out' but occurs in 'in' position in type 'T (of class Box<out T>)'.
    fun set(v: T) { value = v }
               ^
e2.kt:8:17: error: type parameter 'T' is declared as 'in' but occurs in 'out' position in type 'T (of interface Sink<in T>)'.
    fun last(): T
                ^
自作クラスの形 付けるもの 例
T を返すメソッドだけ(読み取り専用) out 結果を入れる入れ物、読み取り専用のコレクション
T を受け取るメソッドだけ in 比較、書き込み先、イベントの受け口
T を受け取り、返しもする 付けない(不変) 読み書きできるコレクション

使う場所で out・in を付ける(型投影)とスター投影

MutableList のような不変のクラスでも、関数の引数の型に out や in を付けて、その関数の中だけ使い方を絞ることができます。これを型投影と呼びます。

fun copy(from: MutableList<out Number>, to: MutableList<in Int>) {
    for (n in from) to.add(n.toInt())     // from からは Number として読める / to へは Int を入れられる
}

fun describe(list: MutableList<*>): String {
    val first: Any? = list.firstOrNull()  // 取り出すと Any?
    return "size=${list.size}, first=$first"
}

fun main() {
    val doubles: MutableList<Double> = mutableListOf(1.5, 2.9)
    val anys: MutableList<Any> = mutableListOf("x")
    copy(doubles, anys)
    println(anys)
    println(describe(doubles))
    println(describe(mutableListOf("a", "b")))
}
[x, 1, 2]
size=2, first=1.5
size=2, first=a

MutableList<out Number> には Double のリストを、MutableList<in Int> には Any のリストを渡せています。その代わり、絞った向きと逆の操作はコンパイルエラーになります。

fun addAll(list: MutableList<out Number>) {
    list.add(1)
}

fun readInt(list: MutableList<in Int>) {
    val n: Int = list[0]
}

fun star(list: MutableList<*>) {
    list.add("x")
    val s: String = list[0]
}
e3.kt:2:14: error: receiver type 'MutableList<out Number>' contains out projection which prohibits the use of 'fun add(element: E): Boolean'.
    list.add(1)
             ^
e3.kt:6:16: error: initializer type mismatch: expected 'Int', actual 'CapturedType(in Int)'.
    val n: Int = list[0]
               ^
e3.kt:10:14: error: receiver type 'MutableList<*>' contains star projection which prohibits the use of 'fun add(element: E): Boolean'.
    list.add("x")
             ^^^
e3.kt:11:19: error: initializer type mismatch: expected 'String', actual 'CapturedType(*)'.
    val s: String = list[0]
                  ^
引数の型 渡せるリスト add(渡す) get(取り出す)
MutableList<Number> Number のリストだけ Number を入れられる Number
MutableList<out Number> Number・Int・Double などのリスト 使えない Number
MutableList<in Int> Int・Number・Any のリスト Int を入れられる Any?
MutableList<*> どのリストでも 使えない Any?

スター投影 * は「型引数が何かは分からないが、安全に使える範囲で扱う」という書き方です。 公式ドキュメントでは、不変の Foo<T : TUpper> に対する Foo<*> は、読み取りでは Foo<out TUpper>、書き込みでは Foo<in Nothing> と同じ、と説明されています。サイズを調べる、中身を表示するなど、要素の型に関係しない処理に向いています。

型消去:is T が書けない理由と reified での解決

JVM 上の Kotlin では、型引数は実行時には残りません(型消去)。そのため、通常の関数の中で「x は T か」を調べようとするとコンパイルエラーになります。

fun <T> isType(x: Any): Boolean = x is T

fun <T> typeName(): String = T::class.simpleName ?: "?"

fun <reified T> notInline(x: Any) = x is T

fun check(x: Any) {
    if (x is List<String>) println("list")
    if (x is List<*>) println("ok")
}
e4.kt:1:40: error: cannot check for instance of erased type 'T (of fun <T> isType)'.
fun <T> isType(x: Any): Boolean = x is T
                                       ^
e4.kt:3:30: error: cannot use 'T' as reified type parameter. Use a class instead.
fun <T> typeName(): String = T::class.simpleName ?: "?"
                             ^^^^^^^^
e4.kt:5:6: error: only type parameters of inline functions can be reified.
fun <reified T> notInline(x: Any) = x is T
     ^^^^^^^
e4.kt:8:14: error: cannot check for instance of erased type 'List<String>'.
    if (x is List<String>) println("list")
             ^^^^^^^^^^^^
エラー文 原因 直し方
cannot check for instance of erased type 'T ...' T は実行時に残らない 関数を inline にして T に reified を付ける
cannot use 'T' as reified type parameter. Use a class instead. T::class などに通常の T を使った 同上、または KClass を引数で受け取る
only type parameters of inline functions can be reified. reified だけ付けて inline がない inline fun にする
cannot check for instance of erased type 'List<String>' 型引数つきの is is List<*> で調べ、要素は個別に確かめる

inline + reified で書き直す

inline 関数は、呼び出した場所に関数の中身が展開されます。展開先では型引数が具体的な型として分かっているので、reified を付けた T は is や T::class に使えます。

inline fun <reified T> List<*>.onlyOf(): List<T> {
    val result = mutableListOf<T>()
    for (e in this) if (e is T) result.add(e)
    return result
}

inline fun <reified T> typeName(): String = T::class.simpleName ?: "?"

inline fun <reified T : Enum<T>> parseEnum(name: String): T? =
    enumValues<T>().firstOrNull { it.name.equals(name, ignoreCase = true) }

enum class Status { OPEN, DONE }

fun main() {
    val mixed: List<Any?> = listOf(1, "a", 2.5, "b", null, 3)
    println(mixed.onlyOf<String>())
    println(mixed.onlyOf<Int>())
    println(mixed.filterIsInstance<Number>())   // 標準ライブラリにも同じ仕組みの関数がある
    println(typeName<Map<String, Int>>())
    println(parseEnum<Status>("done"))
    println(parseEnum<Status>("none"))

    val strings: List<String> = listOf("a")
    val ints: List<Int> = listOf(1)
    println(strings.javaClass == ints.javaClass)  // 実行時はどちらも同じクラス
}
[a, b]
[1, 3]
[1, 2.5, 3]
Map
DONE
null
true

最後の行が型消去そのものです。List<String> と List<Int> は、実行時には同じクラスのオブジェクトです。また typeName<Map<String, Int>>() の結果が Map だけになっているとおり、reified で分かるのは一番外側のクラスまでで、その内側の型引数は実行時には残っていません。

キャストの警告は型消去のサイン

型引数つきの型へ as でキャストすると、コンパイルは通りますが警告が出ます。実行時に確かめられるのは「List であること」までだからです。

fun main() {
    val raw: Any = listOf(1, 2, 3)
    val strings = raw as List<String>
    println(strings.size)
    println(strings[0].length)
}
e7.kt:3:23: warning: unchecked cast of 'Any' to 'List<String>'.
    val strings = raw as List<String>
                      ^^^^^^^^^^^^^^^
3
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
    at E7Kt.main(e7.kt:5)
    at E7Kt.main(e7.kt)

キャストの行(3行目)と size の行は通り、要素を String として使った5行目で止まります。unchecked cast の警告が出たら、filterIsInstance<String>() のように要素ごとに確かめる書き方へ置き換えると、実行時に止まる心配がなくなります。

where で型制約を複数付ける

山かっこの中に書ける上限は1つです。「Named であり、かつ比べられる」のように条件が2つ以上あるときは、where 句に並べます。

interface Named { val name: String }

data class Member(override val name: String, val rank: Int) : Named, Comparable<Member> {
    override fun compareTo(other: Member) = rank.compareTo(other.rank)
}

fun <T> topName(items: List<T>): String where T : Named, T : Comparable<T> =
    items.max().name

class Registry<T> where T : Named, T : Comparable<T> {
    private val items = mutableListOf<T>()
    fun add(item: T) { items.add(item) }
    fun sortedNames(): List<String> = items.sorted().map { it.name }
}

fun <T> longerThan(list: List<T>, threshold: T): List<String>
    where T : CharSequence, T : Comparable<T> =
    list.filter { it > threshold }.map { "$it(${it.length})" }

fun main() {
    val members = listOf(Member("佐藤", 2), Member("鈴木", 5), Member("高橋", 1))
    println(topName(members))
    val r = Registry<Member>()
    members.forEach(r::add)
    println(r.sortedNames())
    println(longerThan(listOf("apple", "kiwi", "pear"), "banana"))
}
鈴木
[高橋, 佐藤, 鈴木]
[kiwi(4), pear(4)]

topName の中では、Comparable のおかげで items.max() が、Named のおかげで .name が使えています。where は関数にもクラスにも書けます。

制約を満たさない型を渡したときのエラー文です。Tag は Named だけを実装し、String は Comparable だけを満たしています。

interface Named { val name: String }
class Tag(override val name: String) : Named

fun <T> topName(items: List<T>): String where T : Named, T : Comparable<T> = items.max().name

fun main() {
    topName<Tag>(listOf(Tag("a")))
    topName<String>(listOf("a", "b"))
}
e5.kt:7:13: error: type argument is not within its bounds: type parameter 'T (of fun <T : Named, Comparable<T>> topName)' must be subtype of 'Named & Comparable<Tag>', but actual: 'Tag'.
    topName<Tag>(listOf(Tag("a")))
            ^^^
e5.kt:8:13: error: type argument is not within its bounds: type parameter 'T (of fun <T : Named, Comparable<T>> topName)' must be subtype of 'Named & Comparable<String>', but actual: 'String'.
    topName<String>(listOf("a", "b"))
            ^^^^^^

(同じ行に出る「inapplicable candidate(s)」の2件は省略しています。)エラー文の Named & Comparable<Tag> が「両方を満たす型」を表しています。

T と T : Any の違い:null を入れられるか

上限を書かない T の上限は Any? です。つまり、何も書かなければ T には String? のような null 許容型も入ります。null を入れさせたくないときは T : Any と書きます。

fun <T> describe(x: T): String = "値=$x"                 // T の上限は Any?(null も入る)
fun <T : Any> describeNonNull(x: T): String = "値=$x / 型=${x::class.simpleName}"
fun <T> orDefault(x: T, default: T & Any): T & Any = x ?: default

class Holder<T>(val value: T)
class StrictHolder<T : Any>(val value: T)

fun main() {
    println(describe(null))
    println(describe<String?>(null))
    println(describeNonNull("abc"))
    println(orDefault<String?>(null, "既定値").length)
    println(orDefault("abc", "既定値").length)

    val h: Holder<String?> = Holder(null)
    println(h.value?.length)
    val s = StrictHolder("abc")
    println(s.value.length)
}
値=null
値=null
値=abc / 型=String
3
3
null
3

T : Any を付けた側に null や null 許容型を渡すと、次のエラーになります。上限なしの T では x::class も書けません。

fun <T> cls(x: T) = x::class
fun <T : Any> describeNonNull(x: T): String = "値=$x"
class StrictHolder<T : Any>(val value: T)

fun main() {
    val h = StrictHolder<String?>(null)
    val name: String? = null
    describeNonNull(name)
}
e6.kt:1:21: error: expression in class literal has nullable type 'T (of fun <T> cls)'. Use '!!' to make the type non-nullable.
fun <T> cls(x: T) = x::class
                    ^
e6.kt:6:26: error: type argument is not within its bounds: type parameter 'T (of class StrictHolder<T : Any>)' must be subtype of 'Any', but actual: 'String?'.
    val h = StrictHolder<String?>(null)
                         ^^^^^^^
…(省略)…
e6.kt:8:21: error: argument type mismatch: actual type is 'String?', but 'Any' was expected.
    describeNonNull(name)
                    ^^^^
書き方 T に入る型 関数の中での x の扱い 使いどころ
<T> null 許容型も入る(上限 Any?) null かもしれない値として扱う 入れ物、そのまま受け渡すだけの関数
<T : Any> null を許さない型だけ null でない値として扱える x::class など、null でないことが前提の処理
T & Any T から null を除いた型 引数や戻り値の一部だけ null なしにする null なら既定値を返す、といった関数

T & Any は「絶対に null でない T」を表す書き方です。上の orDefault は、T に String? を指定しても戻り値は String になるので、.length をそのまま呼べています。

Java のワイルドカードとの対応表

Java では、変位を使う場所ごとにワイルドカードで書きます。Kotlin の型投影がこれに対応します。公式ドキュメントでも、Array<out T> は Java の ? extends に、Array<in T> は ? super に対応すると説明されています。

Java Kotlin 意味
List<? extends Number> List<out Number> / MutableList<out Number> 取り出すだけ(共変)
List<? super Integer> MutableList<in Int> 渡すだけ(反変)
List<?> List<*> 中身の型を問わない
<T extends Comparable<T>> <T : Comparable<T>> 上限を1つ
<T extends Named & Comparable<T>> where T : Named, T : Comparable<T> 制約を複数
クラス宣言には書けない(使う場所ごとに書く) interface Source<out T> 宣言側で変位を決める
Class<T> を引数で渡す inline fun <reified T> 実行時に型を使う

Java 側のコードも動かして、同じ意味になることを確かめています。

import java.util.*;

public class W {
    static double sum(List<? extends Number> src) {      // Kotlin: List<out Number>
        double s = 0;
        for (Number n : src) s += n.doubleValue();
        return s;
    }
    static void fill(List<? super Integer> dst) {        // Kotlin: MutableList<in Int>
        dst.add(1);
        dst.add(2);
    }
    static int size(List<?> any) { return any.size(); }  // Kotlin: List<*>

    public static void main(String[] args) {
        System.out.println(sum(List.of(1.5, 2.5)));
        List<Object> objs = new ArrayList<>();
        fill(objs);
        System.out.println(objs);
        System.out.println(size(List.of("a", "b", "c")));
    }
}
4.0
[1, 2]
3

Java の List<String> は読み書きできるリストなので、Kotlin の MutableList と同じく不変です。Java でも List<Object> objs = strings; は「incompatible types: List<String> cannot be converted to List<Object>」というコンパイルエラーになります(javac 21 で確認)。Kotlin の List が代入できるのは、宣言に out が付いているからで、ここが Java との一番の違いです。Java では引数を受け取るたびに ? extends と書く必要がある場面でも、Kotlin では List<Fruit> と書くだけで Apple のリストを渡せます。

out・in・*・reified・where の選び方

やりたいこと 選ぶもの
自作クラスが T を返すだけ 宣言に out
自作クラスが T を受け取るだけ 宣言に in
既存の不変クラス(MutableList、Array)を読み取り専用で受けたい 引数の型に out(型投影)
既存の不変クラスへ書き込むだけ 引数の型に in(型投影)
要素の型に関係しない処理 *(スター投影)
関数の中で is T や T::class を使いたい inline + reified
T に複数の条件を付けたい where
T に null を入れさせたくない T : Any

よくある質問

Q. out と in のどちらを付けるか迷ったら? A. クラスのメソッドを見て、T が戻り値にだけ出てくるなら out、引数にだけ出てくるなら in、両方に出てくるなら何も付けません。間違えて付けても「declared as 'out' but occurs in 'in' position」のようにコンパイラが教えてくれるので、まず付けてみて確かめる進め方もできます。

Q. reified はクラスの型引数にも付けられますか? A. 付けられるのは inline 関数の型引数だけです。class C<reified T> と書くと「only type parameters of inline functions can be reified.」になります。クラスの中で型を使いたいときは、inline + reified のファクトリ関数を用意して、T::class(KClass)をコンストラクタへ渡す形にします。

Q. List<*> と List<Any?> は何が違いますか? A. List<Any?> は「何でも入るリスト」、List<*> は「何かの型のリストだが、その型は分からない」という意味です。読み取り専用の List ではどちらも要素を Any? として取り出せますが、MutableList では差が出ます。MutableList<Any?> には何でも add でき、MutableList<*> には add できません。

Q. 型引数を省略できるのはどんなときですか? A. 引数や代入先の型から推論できるときです。Box(21) や listOf("a") は推論されます。val x = emptyList() のように手がかりがない場合(「cannot infer type for type parameter 'T'. Specify it explicitly.」になります)や、parseEnum<Status>("done") のように引数に T が現れない場合は、明示します。

まとめ

  • ジェネリクスは型を引数にして、1つのコードを安全に使い回す仕組み。型引数はたいてい推論される
  • 取り出すだけなら out、渡すだけなら in。List は out が付いているので子の型のリストを親の型のリストへ代入でき、MutableList は不変なので代入できない
  • 不変のクラスも、引数の型に out・in を付ければその関数の中だけ絞れる(型投影)。型を問わないなら *
  • 型引数は実行時に残らない。is T や T::class を使うには inline + reified
  • unchecked cast の警告は型消去のサイン。要素ごとに確かめる書き方に置き換える
  • 制約が複数なら where。上限なしの T は null も入るので、入れさせたくないなら T : Any
  • Java の ? extends は out、? super は in、? は * に対応。Kotlin は宣言側にも変位を書ける

参考・出典

  • Kotlin 公式ドキュメント「Generics: in, out, where」 https://kotlinlang.org/docs/generics.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlinジェネリクスの使い方 in・out・reified・whereを図解」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?