- Kotlin
Kotlinジェネリクスの使い方 in・out・reified・whereを図解
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期連続の黒字決算。