Skip to content

Kotlin チートシート

モダンな JVM 言語、簡潔で完全な Java 相互運用性。

01

基礎

変数と型

val(不変)を var(可変)より優先してください。Kotlin は型を推論しますが明示的なアノテーションも許可します。型チェックには 'is' を使用します(自動的にスマートキャスト)。Kotlin 構文ではすべてがオブジェクトです—プリミティブはありません。

kotlin
val name = "Alice"   // immutable (preferred)
var age = 30          // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List<Int> = listOf(1, 2, 3)
println(name::class)  // class kotlin.String
println(age is Int)   // true

文字列テンプレート

単純な変数には $var、式には ${expr} を使用します。三重引用符文字列は改行を保持します—きれいなフォーマットのために trimIndent() を使用します。文字列テンプレートによりフォーマット文字列なしで文字列構築が簡潔で読みやすくなります。

kotlin
val name = "Alice"
val age = 30
println("Name: $name, Age: $age")  // simple var
println("Length: ${name.length}")  // expression
println("Upper: ${name.uppercase()}")
val multiline = """
  Hello, $name
  Age: $age
""".trimIndent()

Null 安全性

Kotlin の null 安全性:? が nullable 型をマーク、?. がセーフコール(null の場合 null を返す)、!! が非 null を表明(null の場合 NPE をスロー)、?: が elvis(null の場合デフォルト)。これによりコンパイル時に NullPointerException を排除します—Kotlin のコア機能です。

kotlin
var name: String? = null  // nullable type
println(name?.length)     // null (safe call)
name = "Alice"
println(name!!.length)    // 5 (non-null assertion)
val len: Int = name?.length ?: 0  // elvis operator
// Non-null types can never be null
val s: String = "hi"  // not nullable

型変換

Kotlin は明示的な型変換を要求します(Java のような暗黙の拡大なし)。変換には toInt()/toDouble()/toString() を使用します。toIntOrNull() は失敗時に null を返します—例外を投げる toInt() より安全です。解析からの null を常に処理してください。

kotlin
val n: Int = 42
val d: Double = n.toDouble()
val s: String = n.toString()
val fromStr: Int? = "100".toIntOrNull()
val invalid: Int? = "abc".toIntOrNull()  // null
println(d, s, fromStr, invalid)

型チェックとスマートキャスト

is が型をチェックし、そのブランチで変数をスマートキャストします—明示的なキャストは不要です。!is が否定です。安全でないキャストには as を(ClassCastException をスロー)、安全なキャストには as? を使用します(失敗時に null を返す)。スマートキャストは Kotlin の主要な利便性です。

kotlin
fun describe(x: Any): String =
    when (x) {
        is Int -> "Int: ${x + 1}"  // x smart-cast to Int
        is String -> "String of length ${x.length}"
        is List<*> -> "List with ${x.size} items"
        else -> "Unknown"
    }
println(describe(42))      // Int: 43
println(describe("hello")) // String of length 5
02

文字列

一般的な文字列メソッド

Kotlin の文字列は標準ライブラリからの豊富な拡張関数を持ちます。ほとんどは新しい文字列を返します(不変)。手動ループの代わりにこれらを使用してください。uppercase() のような関数はロケール対応です—一貫した結果のために uppercase(Locale.ROOT) を使用します。

kotlin
val s = "Hello, World"
println(s.length)          // 12
println(s.uppercase())     // HELLO, WORLD
println(s.lowercase())     // hello, world
println(s.split(", "))     // [Hello, World]
println(s.replace("o", "0"))  // Hell0, W0rld
println(s.reversed())      // dlroW ,olleH
println(s.startsWith("Hello"))  // true

複数行と Raw 文字列

三重引用符文字列は raw です—エスケープシーケンスは不要です(三重二重引用符を除く)。trimMargin('|') は | の後のテキストのみを保持し、きれいなインデントにします。trimIndent() は共通の空白を削除します。SQL、JSON、HTML コードに最適です。

kotlin
val text = """
    |Hello,
    |World!
""".trimMargin()  // | marks line start
println(text)
val json = """{"name": "Alice", "age": 30}"""
println(json)  // no escape needed for quotes
// trimIndent() removes common leading whitespace

文字列構築

joinToString がセパレータ、プレフィックス、ポストフィックス付きでコレクションを結合する慣用的な方法です。buildString が簡潔な構築のために StringBuilder スコープを提供します。ループ内での + の繰り返しは避けてください—効率のために joinToString または StringBuilder を使用します。

kotlin
val parts = listOf("apple", "banana", "cherry")
println(parts.joinToString(", "))  // apple, banana, cherry
println(parts.joinToString(prefix="[", postfix="]"))
val sb = StringBuilder()
for (p in parts) sb.append(p).append(" ")
println(sb.toString().trim())
// buildString for concise building
val result = buildString {
    parts.forEach { append(it).append(";") }
}

文字列から数値へ

toInt()/toDouble() は無効な入力で NumberFormatException をスローします。toIntOrNull() は null を返します—安全なデフォルトのために ?: と組み合わせます。toBooleanStrict() は 'true'/'false' のみを受け入れます。ユーザー入力や信頼できないデータには OrNull バリアントを使用します。

kotlin
val n = "42".toInt()           // 42 (throws if invalid)
val d = "3.14".toDouble()      // 3.14
val safe = "abc".toIntOrNull() // null
val withDefault = "x".toIntOrNull() ?: 0  // 0
val bool = "true".toBooleanStrict()  // true
println(n, d, safe, withDefault)

正規表現

Regex クラスは Java の Pattern をラップします。find は MatchResult?(最初の一致)を返し、findAll はすべてを返します。matches は完全一致をチェックし、containsMatchIn は部分一致をチェックします。二重エスケープを避けるためパターンに三重引用符文字列を使用します。

kotlin
val email = Regex("[\w.]+@[\w]+\.[a-z]+")
val text = "Contact: [email protected]"
val match = email.find(text)
println(match?.value)  // [email protected]
println(email.matches("[email protected]"))  // true
val replaced = text.replace(Regex("\d+"), "#")
println(email.containsMatchIn(text))  // true
03

データ構造

List

listOf が不変リストを作成し、mutableListOf が可変用です。リストは0始まりです。端には first()/last() を使用し(空でスロー)、安全には firstOrNull() を使用します。contains がメンバーシップをチェックします。変更が必要でない限り不変リストを優先してください。

kotlin
val nums = listOf(1, 2, 3)  // immutable
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable[0] = 0
println(nums.size)         // 3
println(nums.first())      // 1
println(nums.last())       // 3
println(nums.contains(2))  // true
println(nums.indexOf(2))   // 1

Map

mapOf が不変マップを作成し、mutableMapOf が可変用です。'to' 中置がペアを作成します。[] アクセスは nullable V? を返します(欠落時 null)。getOrDefault または getValue(欠落時スロー)を使用します。分解(k, v)で反復します。

kotlin
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"])          // 30
println(ages.getOrDefault("Eve", 0))  // 0
println(ages.containsKey("Alice"))    // true
for ((k, v) in ages) println("$k: $v")
val mutable = mutableMapOf("a" to 1)
mutable["b"] = 2  // add/update

Set

setOf が不変セットを作成し、mutableSetOf が可変用です。union/intersect/subtract が新しいセットを返します。セットは一意性を強制します—要素が存在する場合 add は false を返します。重複排除と集合演算に使用します。LinkedHashSet は挿入順序を保持します。

kotlin
val a = setOf(1, 2, 3)
val b = setOf(3, 4, 5)
println(a union b)        // [1,2,3,4,5]
println(a intersect b)    // [3]
println(a subtract b)     // [1,2]
println(a.contains(2))    // true
val mutable = mutableSetOf(1, 2)
mutable.add(3)  // true if added

Array とプリミティブ配列

arrayOf は Array<T> を作成します(プリミティブはボックス化)。ボックス化なしのプリミティブ配列には intArrayOf/doubleArrayOf を使用します(より良いパフォーマンス)。Array(size) { init } が初期化関数で作成します。配列は可変です、ほとんどのユースケースでは List を優先してください。

kotlin
val arr = arrayOf(1, 2, 3)  // Array<Int>
arr[0] = 10
println(arr.size)  // 3
// Specialized arrays for primitives (no boxing)
val ints = intArrayOf(1, 2, 3)
val doubles = doubleArrayOf(1.0, 2.0)
// Array constructor
val squares = Array(5) { it * it }  // [0,1,4,9,16]
println(squares.toList())

Pair と Triple

Pair と Triple は2〜3の値をグループ化します。'to' 中置が Pair を作成します(マップで一般的)。val (a, b) = pair で分解します。3つ以上の値や名前付きフィールドには data class を使用してください—より明確で保守しやすいです。

kotlin
val pair = "Alice" to 30  // Pair<String, Int>
println(pair.first)   // Alice
println(pair.second)  // 30
val (name, age) = pair  // destructuring
val triple = Triple(1, "a", 2.0)
println(triple.first, triple.third)
// Useful for returning two values
fun minMax(list: List<Int>): Pair<Int, Int> =
    Pair(list.min(), list.max())
04

制御フロー

If / Else(式)

Kotlin では if/else は値を返す式です。これが三項演算子を置き換えます。両方のブランチは互換性のある型を返さなければなりません。簡潔な条件付き代入に使用します。ステートメントとして(else なし)は Unit を返します。

kotlin
val score = 85
val grade = if (score >= 90) "A"
            else if (score >= 80) "B"
            else if (score >= 70) "C"
            else "F"
println(grade)  // B
// if returns a value—no ternary needed
// Both branches must have compatible types

When(Switch)

when は Kotlin の強力な switch です—リテラル、範囲(in)、否定(!in)、複数値(カンマ)、is チェックをサポートします。sealed 型で式として使用する場合、網羅的でなければなりません。サブジェクトなしでは、よりクリーンな if/else チェーンです。

kotlin
val n = 2
val label = when (n) {
    0 -> "zero"
    1, 2, 3 -> "small"
    in 4..10 -> "medium"
    !in 1..100 -> "out of range"
    else -> "large"
}
println(label)  // small
// When without subject = multi-condition if
when {
    n > 0 -> println("positive")
    n < 0 -> println("negative")
    else -> println("zero")
}

For ループと範囲

.. が包括的範囲、until が排他的、downTo がカウントダウン、step が増分を設定します。withIndex() がインデックス付き反復を提供します。for-in は任意の Iterable で動作します。これらの範囲はカウントループに簡潔で読みやすいです。

kotlin
for (i in 0 until 5) print(i)  // 01234 (excludes 5)
for (i in 1..5) print(i)      // 12345 (includes 5)
for (i in 5 downTo 1) print(i)  // 54321
for (i in 1..10 step 2) print(i)  // 13579
val list = listOf("a", "b", "c")
for ((index, value) in list.withIndex()) {
    println("$index: $value")
}

While と Do-While

while は前にチェック、do-while は後にチェック(最低1回実行)。両方とも可変状態が必要です。不変性のために for ループまたは関数型操作(map、filter)を優先してください。カウントが不明な条件駆動ループに while を使用します。

kotlin
var count = 0
while (count < 3) {
    println(count)
    count++
}
var j = 0
do {
    println(j)
    j++
} while (j < 3)
// do-while runs at least once
// Prefer for loops when possible

Break と Continue(ラベル)

continue が次の反復にスキップ、break がループを終了します。ラベル(@outer)がネストしたループからの脱出を可能にします—ループ前に label@ で定義、@label で break/continue。控えめに使用してください、関数へのリファクタリングがしばしばよりクリーンです。

kotlin
for (i in 1..5) {
    if (i == 3) continue  // skip 3
    if (i == 5) break     // stop at 5
    println(i)  // 1, 2, 4
}
// Labeled breaks for nested loops
outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) break@outer
        println("$i,$j")
    }
}
05

関数とラムダ

関数定義

関数は fun name(params): ReturnType を使用します。単一式には式本体(= expr)。デフォルトパラメータと名前付き引数をサポートします。Unit 戻り値型 = 副作用のみ。デフォルトパラメータがオーバーロードの必要性を削減します。

kotlin
fun add(a: Int, b: Int): Int = a + b  // expression body
fun greet(name: String, greeting: String = "Hello"): String {
    return "$greeting, $name!"
}
println(add(3, 4))           // 7
println(greet("Alice"))      // Hello, Alice!
println(greet("Bob", greeting = "Hi"))  // named arg
fun log(msg: String): Unit = println(msg)  // Unit = void

ラムダと高階関数

ラムダ:{ params -> body }。'it' が単一パラメータの略記です。map/filter/reduce のような高階関数にラムダを渡します。末尾ラムダ構文:最後のパラメータが関数の場合、() の外に出せます。これが慣用的な Kotlin です。

kotlin
val square: (Int) -> Int = { x -> x * x }
println(square(5))  // 25
val nums = listOf(1, 2, 3)
println(nums.map { it * 2 })      // [2, 4, 6]
println(nums.filter { it > 1 })   // [2, 3]
println(nums.reduce { a, b -> a + b })  // 6
// 'it' is the implicit single parameter

拡張関数

拡張関数は継承や変更なしに既存のクラス(Java のものも)にメソッドを追加します。静的関数の糖衣構文です。'this' がレシーバを指します。API を流暢にするために使用します。コンパイル時に解決されます(動的ディスパッチなし)。

kotlin
fun String.shout(): String = this.uppercase() + "!"
fun Int.isEven(): Boolean = this % 2 == 0
println("hello".shout())  // HELLO!
println(4.isEven())       // true
// Extensions don't modify the class—they're resolved statically
// Use to add utility methods to any type

Inline と Infix

inline が関数本体をコールサイトにコピーします(ラムダオーバーヘッドを排除)。ホットパスの高階関数に使用します。infix がドット/括弧なしの関数呼び出しを可能にします(a op b)—DSL や 'to'、'in'、'and' のような読みやすい操作に使用します。

kotlin
inline fun measure(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Took ${System.currentTimeMillis() - start}ms")
}
measure { println("working...") }
// Infix functions for readable calls
infix fun Int.times(str: String): String = str.repeat(this)
println(3 times "ab")  // ababab

Vararg と Spread

vararg が可変引数を受け入れます(配列に収集)。* が配列を vararg に展開します。名前付きパラメータは vararg の後に続けます。listOf() のような柔軟な API や printf スタイル関数に使用します。spread 演算子は JS の spread と同等です。

kotlin
fun sum(vararg nums: Int): Int = nums.sum()
println(sum(1, 2, 3, 4))  // 10
// Spread operator to pass array
val arr = intArrayOf(1, 2, 3)
println(sum(*arr))  // 6
// Named/default params can follow vararg
fun greet(vararg names: String, greeting: String = "Hi") =
    names.joinToString { "$greeting, $it!" }
println(greet("A", "B"))
06

クラスと OOP

クラスとコンストラクタ

主コンストラクタはクラスヘッダにあります。val/var パラメータはプロパティになります(getter/setter 付き)。val/var なしでは単なるコンストラクタパラメータです(アクセス不可)。init ブロックが構築時に実行されます。Kotlin が getter/setter を自動生成します。

kotlin
class Person(val name: String, val age: Int) {
    fun greet(): String = "Hi, I'm $name"
    fun isAdult(): Boolean = age >= 18
}
val p = Person("Alice", 30)
println(p.greet())   // Hi, I'm Alice
println(p.name)      // Alice (val = property)
println(p.isAdult()) // true
// val/var in constructor = auto property

Data Class

data class が equals、hashCode、toString、copy、componentN(分解用)を自動生成します。データホルダーに使用します。最低1つの val/var パラメータが必要です。copy() が変更されたクローンを作成します—不変更新に最適です。val (a, b) = point で分解します。

kotlin
data class Point(val x: Int, val y: Int)
val p1 = Point(3, 4)
val p2 = Point(3, 4)
println(p1 == p2)        // true (value equality)
println(p1.copy(x = 5))  // Point(x=5, y=4)
println(p1)              // Point(x=3, y=4)
// Auto: equals, hashCode, toString, copy, componentN
val (x, y) = p1  // destructuring via componentN()

Sealed Class

Sealed class はサブタイプを同じファイル/モジュールに制限します。when 式は網羅的です(コンパイラがすべてのケースをチェック)。有限状態を表す ADT(代数的データ型)に使用します。when と組み合わせて、安全でコンパイルチェックされたパターンマッチングを可能にします。

kotlin
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
object Loading : Result()
fun handle(r: Result): String = when (r) {
    is Success -> "Got ${r.value}"
    is Failure -> "Error: ${r.error}"
    Loading -> "Loading..."
}
// when is exhaustive—compiler enforces all cases

Object と Companion

object がシングルトンを宣言します(1つのインスタンス、遅延初期化)。クラス内の companion object が「静的」メンバーを保持します(ClassName.member でアクセス)。シングルトンには object を、ファクトリメソッドと定数には companion を使用します。Companion はインターフェースを実装できます。

kotlin
object Config {  // singleton
    val version = "1.0"
    fun load() = mapOf("key" to "value")
}
println(Config.version)  // 1.0
class User(val name: String) {
    companion object {
        fun create(name: String) = User(name)
        const val MAX = 100
    }
}
val u = User.create("Alice")  // like static method

継承とインターフェース

クラスはデフォルトで final です—継承を許可するには 'open' を使用します。'override' が必須です。インターフェースはデフォルト実装を持てます。クラスは1つのクラスを拡張し複数のインターフェースを実装します。部分実装には abstract を使用します。継承よりコンポジションを優先します。

kotlin
open class Animal(val name: String) {
    open fun speak() = "..."
}
class Dog(name: String) : Animal(name) {
    override fun speak() = "Woof"
}
interface Greetable {
    val name: String
    fun greet(): String  // can have default impl
}
class Person(override val name: String) : Greetable {
    override fun greet() = "Hi, $name"
}
07

Null 安全性とスコープ関数

セーフコールと Elvis

?. セーフコール(レシーバが null の場合 null を返す)、?: elvis(デフォルトを提供)。深いアクセスに ?. をチェーンします(user?.address?.city)。早期退出には ?: return/throw を使用します。これらが null 処理を簡潔で安全にします—明示的な null チェックは不要です。

kotlin
var name: String? = "Alice"
println(name?.length)      // 5
name = null
println(name?.length)      // null
println(name?.length ?: 0) // 0 (elvis: default if null)
println(name?.length ?: return)  // early return
// Safe call chain
val city: String? = user?.address?.city

let(Null チェック)

let は値が非 null の場合のみブロックを実行します。'it' がブロック内の非 null 値です。if-null チェックの代わりに null ガード操作に使用します。一般的なパターン:安全な処理のために value?.let { ... }。ブロックの結果を返します。

kotlin
var name: String? = "Alice"
name?.let {
    println("Name is $it")  // only runs if not null
    println("Length: ${it.length}")  // it is non-null
}
// Common for null-guarded blocks
val result = name?.let {
    process(it)  // it: String (non-null)
} ?: "default"

apply と also

apply はオブジェクトを設定します(オブジェクトを返す、'this' レシーバ)—ビルダーに最適。also は副作用を実行します(オブジェクトを返す、'it' パラメータ)—チェーン内のロギング/デバッグに適しています。両方とも元のオブジェクトを返し、流暢なチェーンを可能にします。

kotlin
val list = mutableListOf<Int>().apply {
    add(1); add(2); add(3)  // 'this' = the list
    println("Built $size items")
}
val config = Config().also {
    it.timeout = 30  // 'it' = config
    println("Configured")
}
// apply: configure and return object (this)
// also: side effects, return object (it)

run と with

run はオブジェクトでブロックを実行します(ブロック結果を返す、'this' レシーバ)—オブジェクトの変換に使用します。with は run に似ていますがオブジェクトをパラメータとして取ります(nullable でチェーン不可)。同じオブジェクトでの操作のグループ化に使用します。

kotlin
val result = "Hello".run {
    length  // 'this' = string, returns last expr
}  // 5
val r2 = with(StringBuilder()) {
    append("a"); append("b")
    toString()  // returns this
}
// run: object.run { } - returns block result
// with: with(obj) { } - returns block result (not chainable)

TakeIf と TakeUnless

takeIf は述語が true の場合オブジェクトを返し、そうでなければ null です。takeUnless は逆です。チェーン内の条件付きフィルタリングに使用します—個別の if チェックを回避します。デフォルトには ?: と組み合わせます。検証パイプラインにエレガントです。

kotlin
val age = 25
val valid = age.takeIf { it >= 18 }  // 25 (if true)
val invalid = age.takeIf { it < 18 }  // null (if false)
val adult = age.takeUnless { it < 18 }  // 25
// Useful for filtering in chains
val email = getUser()?.email?.takeIf { it.contains("@") }
println(valid, invalid, adult)
08

コレクションと関数型

Map / Filter / Fold

map が変換、filter が選択、reduce/fold が集約します。'it' が暗黙の要素です。fold はシードを取り、reduce は取りません(空でスロー)。これらが関数型コレクション処理のコアです—明確さのためにループの代わりに使用します。

kotlin
val nums = listOf(1, 2, 3, 4, 5)
println(nums.map { it * 2 })          // [2,4,6,8,10]
println(nums.filter { it % 2 == 0 })  // [2,4]
println(nums.reduce { a, b -> a + b }) // 15
println(nums.fold(0) { a, b -> a + b }) // 15
println(nums.sum())                   // 15
println(nums.joinToString(", "))      // 1, 2, 3, 4, 5

FlatMap と GroupBy

flatten がネストの1レベルを削除します。flatMap はマップしてフラット化を1ステップで行います—ネストした変換に不可欠です。groupBy がキーで Map に分割します。これらはデータ処理と分析パイプラインに強力です。

kotlin
val nested = listOf(listOf(1, 2), listOf(3, 4))
println(nested.flatten())         // [1,2,3,4]
println(nested.flatMap { it.map { n -> n * 2 } })  // [2,4,6,8]
val words = listOf("apple", "bat", "ant")
val byFirst = words.groupBy { it.first() }
// {a=[apple, ant], b=[bat]}
println(byFirst)

ソート

sorted/sortedDescending が自然順序でソートします。sortedBy/sortedByDescending がキーセレクタでソートします。これらは新しいリストを返します(不変)。可変リストには sort/sortBy を使用します(インプレース)。特定フィールドでソートするにはキーセレクタを使用します。

kotlin
val nums = listOf(3, 1, 4, 1, 5)
println(nums.sorted())           // [1,1,3,4,5]
println(nums.sortedDescending()) // [5,4,3,1,1]
val people = listOf("Alice" to 30, "Bob" to 25)
val byAge = people.sortedBy { it.second }  // by age
println(byAge)
val byNameDesc = people.sortedByDescending { it.first }
println(byNameDesc)

Sequence(遅延)

Sequence は遅延です—操作は終端操作(toList、sum、count)まで遅延されます。大きなデータのパフォーマンス向上のため中間コレクションを回避します。大きなコレクションの多段パイプラインには asSequence() を使用します。Java Stream のようなものです。

kotlin
val nums = (1..1000000).toList()
// Eager: creates intermediate lists
val eager = nums.filter { it % 2 == 0 }.map { it * 2 }.take(5)
// Lazy: no intermediate collections
val lazy = nums.asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .toList()  // forces evaluation
println(lazy)  // [4, 8, 12, 16, 20]

Partition と Chunked

partition が述語で2つのリストに分割します(Pair を返す)。chunked が固定サイズのリストに分割します。windowed がスライディングウィンドウを作成します。これらはバッチ処理、ページネーション、スライディングウィンドウアルゴリズムに有用です。すべて新しいコレクションを返します。

kotlin
val nums = listOf(1, 2, 3, 4, 5)
val (evens, odds) = nums.partition { it % 2 == 0 }
println(evens)  // [2, 4]
println(odds)   // [1, 3, 5]
val chunked = nums.chunked(2)
println(chunked)  // [[1, 2], [3, 4], [5]]
val windowed = nums.windowed(3)
println(windowed)  // [[1,2,3], [2,3,4], [3,4,5]]
09

コルーチンと Async

Launch(ファイアアンドフォーゲット)

launch は結果を返さないコルーチンを開始します(ファイアアンドフォーゲット)。delay は非ブロッキングです(Thread.sleep と異なる)。runBlocking が同期/非同期コードをブリッジします(main/テストで使用)。コルーチンは軽量です—数千が数スレッドで実行できます。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        delay(1000)
        println("World!")  // after 1s
    }
    println("Hello")  // immediately
    // launch doesn't return a result
    // Use for side-effect coroutines
}
// Output: Hello, then World! after 1s

Async と Await

async は Deferred<T> を返すコルーチンを開始します。await() は結果が準備できるまでサスペンドします。並列処理のために await の前に複数の async を開始します。結果を生成する並行計算に使用します。他言語の Promise/Future のようなものです。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val deferred = async {
        delay(1000)
        42  // return value
    }
    val result = deferred.await()
    println(result)  // 42
    // Parallel execution
    val a = async { computeA() }
    val b = async { computeB() }
    println(a.await() + b.await())  // concurrent
}

Suspend 関数

suspend 関数はスレッドをブロックせずに一時停止・再開できます。コルーチンまたは他の suspend 関数からのみ呼び出せます。coroutineScope が構造化スコープを提供します(すべての子を待機)。非同期 API に使用します—非同期コードを同期的に見せます。

kotlin
import kotlinx.coroutines.*
suspend fun fetchUser(id: Int): String {
    delay(500)  // simulates network
    return "User $id"
}
suspend fun fetchAll(): List<String> = coroutineScope {
    val a = async { fetchUser(1) }
    val b = async { fetchUser(2) }
    listOf(a.await(), b.await())
}
// suspend functions can only be called from coroutines

Flow(コールドストリーム)

Flow は Kotlin のコールド非同期ストリームです(RxJava Observable のような)。値は収集時に生成されます。map/filter/reduce オペレータを使用します。emit が生成、collect が消費します。ストリーミングデータ、イベント、ページネーション API に最適です。ホットストリームには SharedFlow/StateFlow を使用します。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)  // produce value
    }
}
fun main() = runBlocking {
    numbers().collect { println(it) }  // 1,2,3,4,5
    // Transform
    numbers().map { it * 2 }.filter { it > 4 }.collect {
        println(it)  // 6, 8, 10
    }
}

Dispatcher とコンテキスト

Dispatcher がスレッドプールを選択します:Main は UI、IO はネットワーク/ファイル(大規模プール)、Default は CPU 作業(コア数)。withContext がコルーチン内でコンテキストを切り替えます。UI のブロックやスレッドプールの枯渇を避けるため適切な dispatcher を使用します。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    launch(Dispatchers.Main) { /* UI thread */ }
    launch(Dispatchers.IO) {
        // Network/file I/O (thread pool)
        val data = fetchData()
    }
    launch(Dispatchers.Default) {
        // CPU-intensive work
        val result = heavyCompute()
    }
    // withContext switches dispatcher
    val data = withContext(Dispatchers.IO) { readFile() }
}
10

エラー処理と I/O

Try / Catch / Finally

try/catch/finally は Java に似ていますが try は値を返す式です。チェック例外はありません—すべて非チェックです。的を絞った処理のために特定の例外をキャッチします。クリーンアップには finally を使用します。予期される失敗には Result または nullable 戻り値を優先します。

kotlin
val result = try {
    "abc".toInt()
} catch (e: NumberFormatException) {
    0  // fallback
} finally {
    println("cleanup")
}
println(result)  // 0
// try is an expression—returns a value
// Kotlin doesn't have checked exceptions

Result 型

Result<T> は成功または失敗をラップします(Scala の Try のような)。runCatching が例外を Result に変換します。安全なアクセスには getOrNull/getOrElse。コールバックには onSuccess/onFailure。例外の代わりに予期されるエラーに使用します—よりクリーンな関数型エラー処理です。

kotlin
fun parse(s: String): Result<Int> =
    runCatching { s.toInt() }
val r = parse("42")
println(r.getOrNull())    // 42
println(r.getOrElse { 0 }) // 42
r.onSuccess { println("OK: $it") }
    .onFailure { println("Err: ${it.message}") }
val r2 = parse("abc")
println(r2.getOrNull())   // null

カスタム例外

カスタム例外は Exception(またはサブクラス)を拡張します。デバッグのためにメッセージを渡します。汎用の前に特定の例外をキャッチします(順序が重要)。真に例外的なケースに例外を使用します、予期される失敗には Result または nullable 戻り値を優先します。

kotlin
class InvalidAgeException(message: String) : Exception(message)
fun setAge(age: Int) {
    if (age < 0) throw InvalidAgeException("Age cannot be negative: $age")
}
try {
    setAge(-5)
} catch (e: InvalidAgeException) {
    println("Caught: ${e.message}")
} catch (e: Exception) {
    println("Other: ${e.message}")
}

ファイル I/O

Kotlin は拡張関数付きで Java の File を使用します。単純なケースには writeText/readText。useLines が行をストリームします(自動クローズ、メモリ効率的)。大きなファイルには bufferedReader() を使用します。リソースは常に閉じてください—自動クローズのために use { } ブロックを使用します。

kotlin
import java.io.File
// Write
File("test.txt").writeText("Hello, File!")
// Read
val content = File("test.txt").readText()
println(content)  // Hello, File!
// Append
File("log.txt").appendText("new line\n")
// Line by line
File("test.txt").useLines { lines ->
    lines.forEach { println(it) }
}

JSON(kotlinx.serialization)

kotlinx.serialization は Kotlin の公式 JSON ライブラリです—コンパイル時安全、リフレクションなし。data class に @Serializable をアノテートします。JSON に encodeToString/decodeFromString。カスタムキー名には @SerialName、デフォルトには @Optional を使用します。代替:Gson/Moshi。

kotlin
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class User(val name: String, val age: Int)
val user = User("Alice", 30)
val json = Json.encodeToString(user)
println(json)  // {"name":"Alice","age":30}
val decoded = Json.decodeFromString<User>(json)
println(decoded.name)  // Alice
// Requires @Serializable annotation
11

コルーチンの深掘り

構造化並行性

構造化並行性は子コルーチンを親スコープに結び付けます—親はすべての子が完了するまで完了せず、子の失敗は兄弟をキャンセルします。GlobalScope より coroutineScope を優先します。これがコルーチンリークを防ぎ、キャンセルを予測可能にします。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    // coroutineScope waits for all children
    coroutineScope {
        launch { delay(1000); println("A") }
        launch { delay(500); println("B") }
    }
    println("Done")  // after both complete
    // If a child fails, parent cancels siblings
    // Never use GlobalScope unless necessary
}

キャンセルと協調的キャンセル

キャンセルは協調的です—コルーチンはサスペンションポイント(delay、yield、await)でチェックしなければなりません。キャンセル可能なサスペンド関数は CancellationException をスローします。ensureActive() または isActive でチェックします。非サスペンド CPU ループは yield() を呼ぶか isActive をチェックしない限りキャンセルに応答しません。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            delay(300)
            yield()  // explicit suspension point
            println("Working $i")
        }
    }
    delay(700)
    job.cancelAndJoin()  // cancels and waits
    println("Cancelled")
}
// delay/yield check for cancellation
// isActive checks cancellation state

例外処理

launch の未処理例外は CoroutineExceptionHandler がインストールされていない限り親に伝播します(兄弟をキャンセル)。async は例外を await() に延期します。await の周りに try/catch を使用します。ハンドラは launch の未キャッチ例外にのみ機能します—失敗を分離するには SupervisorJob を使用します。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Caught: $e")
    }
    val job = launch(handler) {
        throw RuntimeException("Boom!")
    }
    job.join()
    // async exceptions surface on await()
    val deferred = async { throw IllegalStateException() }
    try { deferred.await() } catch (e: Exception) {
        println("Async failed: $e")
    }
}

スーパービジョン(supervisorScope)

supervisorScope(と SupervisorJob)は子の失敗が兄弟をキャンセルしないスコープを作成します—各子が独立して失敗します。独立した操作(複数の独立した API 呼び出しなど)に使用します。通常の coroutineScope は最初の失敗で兄弟をキャンセルします(フェイルファスト)。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    // supervisorScope: child failures don't cancel siblings
    supervisorScope {
        launch { delay(100); throw RuntimeException("A fails") }
        launch { delay(200); println("B still runs") }
    }
    // vs coroutineScope: A's failure cancels B
    // Use SupervisorJob for long-lived services
    val scope = CoroutineScope(SupervisorJob())
}

Channel(CSP スタイル)

Channel はコルーチン間で値を渡します(Go チャネルのような)。Rendezvous(容量 0)が送信者と受信者を同期し、バッファ付きはキューイングを許可します。満杯で send がサスペンドし、空で receive がサスペンドします。プロデューサは常に close() してください。ファンアウトには BroadcastChannel または SharedFlow を使用します。ほとんどのストリーミングニーズには Flow を優先します。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
    val channel = Channel<Int>(3)  // buffered capacity 3
    launch {
        for (i in 1..5) {
            channel.send(i)
            println("Sent $i")
        }
        channel.close()
    }
    // receive() blocks until available
    for (x in channel) println("Got $x")
    // Produce-consume pattern
    // Rendezvous (cap 0) syncs sender/receiver
}
12

Flow の深掘り

Flow オペレータ

Flow オペレータはコールドです—収集時にのみ実行されます。map/filter/take は Stream/Sequence のようなものです。transform が最も柔軟です(入力ごとに複数の値を emit 可能)。flatMap バリアントがネストした flow を処理します:concat(シーケンシャル)、merge(並行)、latest(前をキャンセル)。順序/並行性ニーズに基づいて選択します。

kotlin
import kotlinx.coroutines.flow.*
fun nums() = flow {
    for (i in 1..5) emit(i)
}
suspend fun main() {
    nums().map { it * it }           // 1,4,9,16,25
        .filter { it > 5 }          // 9,16,25
        .take(2)                    // 9,16
        .collect { println(it) }
    // transform: emit multiple values
    nums().transform { x ->
        emit(x)
        emit(x * 10)
    }.collect { println(it) }
    // flatMapConcat / flatMapMerge / flatMapLatest
}

Buffer と並行性

buffer が固定容量キューでプロデューサとコンシューマを分離します—生産と消費速度が異なる場合に有用。conflate は最新値のみを保持し(中間をドロップ)UI/状態更新に使用します。collectLatest は新しい値が到着したとき前のコレクタをキャンセルします—検索入力に最適です。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun events() = flow {
    for (i in 1..3) {
        delay(100); emit(i)
    }
}
fun main() = runBlocking {
    // Without buffer: producer & consumer alternate (slow)
    events().buffer()  // decouple producer/consumer
        .collect { delay(200); println(it) }
    // conflate: drop intermediate values
    events().conflate().collect { println(it) }
    // collectLatest: cancel previous collection
    events().collectLatest { delay(200); println(it) }
}

StateFlow と SharedFlow(ホットストリーム)

StateFlow は単一の最新値を保持するホットストリームです—UI 状態に使用します(LiveData を置き換え)。常に値を持ち、conflate します。SharedFlow はホットブロードキャストストリームです(複数コレクタ、初期値不要)—イベントに使用します。replay=1 の SharedFlow はキャッシュされたイベントバスのように動作します。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    // StateFlow: holds one value, stateful
    val state = MutableStateFlow(0)
    launch {
        state.collect { println("State: $it") }
    }
    state.value = 1  // update synchronously
    state.value = 2
    // SharedFlow: broadcasts to multiple collectors
    val events = MutableSharedFlow<String>()
    launch { events.collect { println("A: $it") } }
    launch { events.collect { println("B: $it") } }
    events.emit("Hello")  // both receive
}

Flow 例外処理

catch は上流の例外を処理し、フォールバック値を emit できます。チェーン内でそれより前に emit された例外のみをキャッチします。retry/retryWhen は失敗時に flow を再収集します(ネットワーク呼び出しに有用)。下流の例外には collect を try/catch でラップします。flow ビルダー内でキャッチしないでください—伝播させます。

kotlin
import kotlinx.coroutines.flow.*
fun numbers() = flow {
    emit(1)
    throw RuntimeException("fail")
    emit(2)  // never reached
}
suspend fun main() {
    // catch operator (upstream only)
    numbers().catch { e -> emit(-1) }
        .collect { println(it) }  // 1, -1
    // retry on failure
    numbers().retry(3) { e ->
        println("Retry: $e"); true
    }.collect { println(it) }
    // retryWhen for custom logic
}

flowOn とコンテキスト

flowOn は上流 flow(プロデューサ + その上のオペレータ)の dispatcher を切り替えます。flow がブロッキング I/O を行う場合に不可欠です—flowOn(Dispatchers.IO) でラップします。下流(collect)は呼び出し元のコンテキストで実行されます。複数の flowOn 呼び出しが各セグメントに個別のコンテキストを作成します。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun diskFlow() = flow {
    for (i in 1..3) {
        Thread.sleep(100)  // blocking I/O
        emit(i)
    }
}
suspend fun main() {
    // flowOn changes upstream context
    diskFlow().flowOn(Dispatchers.IO)
        .collect { println(it) }
    // Without flowOn, runs on collector's dispatcher
    // flowOn applies to all operators above it
    diskFlow().map { it * 2 }
        .flowOn(Dispatchers.IO)
        .filter { it > 2 }
        .collect { println(it) }
}
13

Sealed Class と ADT

Sealed Class の基礎

Sealed class はサブクラスを既知のセットに制限します(同じファイル/モジュール)。コンパイラがすべての可能な型を知り、else なしの網羅的 when 式を可能にします。有限状態(Result、UiState、ネットワークレスポンス)のモデリングに最適です。data class と組み合わせて代数的データ型を形成します。

kotlin
sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Failure(val error: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}
fun handle(r: Result<Int>) = when (r) {
    is Result.Success -> println("Got ${r.value}")
    is Result.Failure -> println("Error: ${r.error}")
    Result.Loading -> println("Loading...")
}
// All subclasses defined in same file/module

Sealed Interface(Kotlin 1.5+)

Sealed interface(Kotlin 1.5+)は sealed をインターフェースに拡張し、クラスが複数の sealed 型を実装できるようにします—sealed class より柔軟です。同じファイル/同じモジュール制限が適用されます。型が複数のカテゴリに属するドメインモデリングに有用です。

kotlin
sealed interface Shape {
    fun area(): Double
}
data class Circle(val r: Double) : Shape {
    override fun area() = Math.PI * r * r
}
data class Square(val side: Double) : Shape {
    override fun area() = side * side
}
// Sealed interfaces allow multiple inheritance
sealed interface Clickable { fun click() }
class Button : Shape, Clickable {
    override fun area() = 0.0
    override fun click() = println("Clicked")
}

網羅的 When

式として(ステートメントではなく)使用する場合、コンパイラはすべての sealed ケースを要求します—else は不要です。新しいサブクラスを追加すると未処理の when サイトでコンパイルエラーが発生し、リファクタリングを安全にします。これが状態機械と UI レンダリングのための sealed class のキラーフィーチャーです。

kotlin
sealed class NetworkState {
    object Loading : NetworkState()
    data class Success(val data: String) : NetworkState()
    data class Error(val message: String) : NetworkState()
}
fun render(state: NetworkState): String = when (state) {
    NetworkState.Loading -> "Spinner"
    is NetworkState.Success -> "Data: ${state.data}"
    is NetworkState.Error -> "Error: ${state.message}"
    // No else needed—compiler enforces all cases
}
// Adding a new subclass causes compile error
// until you handle it

ADT モデリング(Result/Either)

Sealed class は鉄道指向プログラミングのために和型(Either/Result)をモデル化します。fold が両方のブランチを処理します。例外と異なり、エラーは型シグネチャで明示的です。Arrow-kt がより豊かな Either/Validated を提供します。try/catch なしの予測可能で合成可能なエラー処理に使用します。

kotlin
sealed class Either<out L, out R> {
    data class Left<out L>(val value: L) : Either<L, Nothing>()
    data class Right<out R>(val value: R) : Either<Nothing, R>()
}
fun <L, R> Either<L, R>.fold(
    ifLeft: (L) -> Unit,
    ifRight: (R) -> Unit
) = when (this) {
    is Either.Left -> ifLeft(value)
    is Either.Right -> ifRight(value)
}
fun divide(a: Int, b: Int): Either<String, Int> =
    if (b == 0) Either.Left("Divide by zero")
    else Either.Right(a / b)

再帰的な Sealed Class

Sealed class は再帰的にでき、ツリー構造(AST、JSON、式)をモデル化します。when + 再帰のパターンマッチングでエレガントに評価します。これが古典的な関数型 ADT 使用法です—型安全、網羅的、リファクタリングフレンドリー。コンパイラ、パーサー、設定 DSL で使用されます。

kotlin
sealed class Expr {
    data class Num(val value: Int) : Expr()
    data class Add(val left: Expr, val right: Expr) : Expr()
    data class Mul(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int = when (e) {
    is Expr.Num -> e.value
    is Expr.Add -> eval(e.left) + eval(e.right)
    is Expr.Mul -> eval(e.left) * eval(e.right)
}
val expr = Expr.Add(Expr.Num(2), Expr.Mul(Expr.Num(3), Expr.Num(4)))
fun main() = println(eval(expr))  // 14
14

委譲プロパティ

lazy 委譲

lazy は初回アクセスまで初期化を遅延し、結果をキャッシュします。デフォルトはスレッドセーフです(ダブルチェックロッキング)。必要ないかもしれない高価なリソース(設定、DB 接続、シングルトン)に使用します。同期オーバーヘッドを避けるため単一スレッドコンテキストには NONE モードを渡します。

kotlin
val heavyConfig: Config by lazy {
    println("Initializing...")
    loadConfigFromFile()  // runs once, first access
}
fun main() {
    println("Before access")
    println(heavyConfig)  // initializes here
    println(heavyConfig)  // cached, no re-init
}
// lazy is thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED)
// Use LazyThreadSafetyMode.NONE for single-threaded

observable と vetoable

observable は各変更後にコールバックを発火します(ロギング、副作用)。vetoable は false を返すことで変更を拒否できます(検証)。両方とも初期値とラムダを取ります。リアクティブ状態、検証、UI 更新のトリガーに使用します。より複雑なシナリオにはカスタム委譲を使用します。

kotlin
import kotlin.properties.Delegates
var count: Int by Delegates.observable(0) { _, old, new ->
    println("Changed: $old -> $new")
}
var age: Int by Delegates.vetoable(0) { _, old, new ->
    new >= 0  // reject negative values
}
fun main() {
    count = 1   // prints "Changed: 0 -> 1"
    count = 2   // prints "Changed: 1 -> 2"
    age = 25    // accepted
    age = -5    // rejected, stays 25
}

カスタムプロパティ委譲

カスタム委譲は ReadOnlyProperty/ReadWriteProperty 経由で getValue(var には setValue も)を実装します。再利用可能なプロパティ振る舞い(検証、フォーマット、キャッシュ、DB マッピング)をカプセル化します。thisRef が所有者、prop がプロパティメタデータです。ORM/シリアライゼーションフレームワークに強力です。

kotlin
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class TrimmedString : ReadWriteProperty<Any?, String> {
    private var value: String = ""
    override fun getValue(thisRef: Any?, prop: KProperty<*>) = value
    override fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        this.value = value.trim()
    }
}
class User {
    var name: String by TrimmedString()
}
fun main() {
    val u = User()
    u.name = "  Alice  "
    println(u.name)  // "Alice"
}

Map バックプロパティ

Map に委譲するとプロパティを動的キーにバインドできます—ボイラープレートなしで JSON、設定、ORM 行を解析するのに有用です。プロパティ名がマップキーと一致しなければなりません。書き込み可能プロパティには MutableMap を使用します。これが kotlinx.serialization や多くの ORM の内部動作です。

kotlin
class User(map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
    val email: String? by map
}
fun main() {
    val u = User(mapOf(
        "name" to "Alice",
        "age" to 30,
        "email" to "[email protected]"
    ))
    println(u.name)  // Alice
}
// Mutable version: MutableMap
class MutableUser(map: MutableMap<String, Any?>) {
    var name: String by map
}

notNull と Singleton 委譲

notNull() は lateinit のようなものですが、任意の型(プリミティブを含む)で val/var で動作します。設定前にアクセスするとスローします。シングルトンには object(即時)または by lazy(遅延)を優先します。lateinit はクラス内の var のみ、notNull 委譲はより柔軟ですがわずかなオーバーヘッドがあります。

kotlin
import kotlin.properties.Delegates
class Service {
    // Late-init for non-null, set once
    var config: String by Delegates.notNull()
    // Throws before initialization
}
// Singleton via object (no delegate needed)
object Database {
    val connection = connect()
}
// Or lazy singleton
val db: Database by lazy { Database() }
fun main() {
    val s = Service()
    // println(s.config)  // IllegalStateException
    s.config = "prod"
    println(s.config)  // prod
}
15

DSL 構築

型安全ビルダー

型安全ビルダーはレシーバ付き関数型(レシーバ付きラムダ)を使用してネストした DSL を作成します。ラムダはレシーバ(this)のコンテキストで実行されるため、そのメソッドを直接呼び出せます。これが Kotlin の HTML DSL、Gradle ビルドスクリプト、kotlinx.html の動作方法です—宣言的でコンパイル時チェック済み。

kotlin
class Table {
    private val rows = mutableListOf<Row>()
    fun row(init: Row.() -> Unit) {
        val r = Row(); r.init(); rows.add(r)
    }
    fun build() = rows
}
class Row {
    private val cells = mutableListOf<String>()
    fun cell(text: String) { cells.add(text) }
    fun build() = cells
}
fun table(init: Table.() -> Unit) = Table().apply(init).build()
val t = table {
    row { cell("A"); cell("B") }
    row { cell("C"); cell("D") }
}

@DslMarker(スコープ制御)

@DslMarker は暗黙のレシーバリークを防ぎます—ネストした DSL ブロック内では、最も内側のレシーバのメソッドのみ呼び出せます。これがないと、Kotlin は外側のレシーバのメソッド呼び出しを許可し、混乱し/バグのある DSL になります。すべての DSL クラスに同じマーカーアノテーションでアノテートします。

kotlin
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML { fun body(b: Body.() -> Unit) { /*...*/ } }
@HtmlDsl
class Body { fun p(text: String) { /*...*/ } }
fun html(init: HTML.() -> Unit) = HTML().apply(init)
// Without @DslMarker, inner 'this' could call outer methods
html {
    body {
        // p() is unambiguous—Body's method
        // body() would be an error (not Body's method)
        p("Hello")
    }
}

中置関数

中置関数が自然言語構文を可能にします(a to b、1 until 10)。単一パラメータのメンバーまたは拡張関数でなければなりません。DSL、テストフレームワーク、数学ライブラリで多用されます。組み込み例:to(Pair)、until/step(範囲)、コレクション操作。

kotlin
infix fun Int.times(str: String) = str.repeat(this)
fun main() {
    println(3 times "ab")  // "ababab"
    // Equivalent to: 3.times("ab")
    // No dot, no parentheses—reads like natural language
}
// Common in testing
infix fun <T> T.shouldEqual(expected: T) =
    assert(this == expected)
fun test() { 5 shouldEqual 5 }
// Also: to, until, step, in, etc.

演算子オーバーロード

演算子オーバーロードによりオブジェクトが +、-、*、[]、() などを自然な構文で使用できます。関数に operator をマークします。賢くオーバーロードしてください—数学型(Vec、Matrix、Money)は恩恵を受けます、任意のオーバーロードは可読性を損ないます。get/set がインデックス付けを可能にし、invoke がオブジェクトを呼び出し可能にします。範囲演算子(rangeTo、contains)が for ループを駆動します。

kotlin
data class Vec(val x: Int, val y: Int) {
    operator fun plus(o: Vec) = Vec(x + o.x, y + o.y)
    operator fun minus(o: Vec) = Vec(x - o.x, y - o.y)
    operator fun times(s: Int) = Vec(x * s, y * s)
    operator fun unaryMinus() = Vec(-x, -y)
    operator fun get(i: Int) = when (i) { 0 -> x; 1 -> y; else -> throw IndexOutOfBoundsException() }
}
fun main() {
    val a = Vec(1, 2)
    val b = Vec(3, 4)
    println(a + b)        // Vec(x=4, y=6)
    println(a * 2)        // Vec(x=2, y=4)
    println(-a)           // Vec(x=-1, y=-2)
    println(a[0])         // 1
}

レシーバ付き関数型

レシーバ付き関数型(A.(B) -> C)によりラムダがレシーバに 'this' としてアクセスできます。これが Kotlin DSL とスコープ関数(apply、run、with)の基盤です。apply はレシーバを返し、run はラムダ結果を返します。これを習得して表現力豊かで型安全な内部 DSL を構築してください。

kotlin
// Lambda with receiver: A.(B) -> C
val greet: String.(Int) -> String = { times -> repeat(times) { this + "!" } }
fun main() {
    println("Hello".greet(3))  // Hello!!!Hello!!!Hello!!!
    // 'this' is the String receiver
}
// Build DSLs with receiver
class StringBuilder {
    private val parts = mutableListOf<String>()
    fun add(s: String) { parts.add(s) }
}
fun build(init: StringBuilder.() -> Unit) =
    StringBuilder().apply(init)
val sb = build { add("a"); add("b") }
16

テスト(JUnit 5、MockK、Turbine)

JUnit 5 の基礎

JUnit 5(Jupiter)は標準の Kotlin テストフレームワークです。@Test がテストをマークし、@BeforeEach/@AfterEach がテストごとのセットアップ/ティアダウンを実行します。可読性のためにバッククォート名を使用します。@ParameterizedTest + @ValueSource が複数入力でテストを実行します。assertThrows が例外をチェックします。@Disabled がテストをスキップします。

kotlin
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.*
class CalculatorTest {
    lateinit var calc: Calculator
    @BeforeEach fun setup() { calc = Calculator() }
    @Test fun `add two numbers`() {
        assertEquals(5, calc.add(2, 3))
    }
    @Test fun `divide by zero throws`() {
        assertThrows<ArithmeticException> { calc.divide(10, 0) }
    }
    @ParameterizedTest
    @ValueSource(ints = [1, 2, 3])
    fun `positive numbers`(n: Int) { assertTrue(n > 0) }
    @Disabled("TODO") @Test fun skip() {}
}

MockK(モック)

MockK は Kotlin の慣用的なモックライブラリです(final class、コルーチン、拡張関数を処理)。every { } returns/throws がスタブし、verify { } が呼び出しをチェックします。relaxed = true で void メソッドのスタブをスキップします。MockK は coEvery/coVerify 経由で suspend 関数をネイティブにサポートします—コルーチンテストに不可欠です。

kotlin
import io.mockk.*
interface UserService { fun find(id: Int): String?; fun save(name: String): Int }
class Test {
    val mock = mockk<UserService>()
    @Test fun test() {
        every { mock.find(1) } returns "Alice"
        every { mock.find(any()) } returns null
        every { mock.save(any()) } throws RuntimeException("fail")
        verify { mock.find(1) }              // called once
        verify(exactly = 2) { mock.find(any()) }
        confirmVerified(mock)
    }
}
// relaxUnitFun / relaxed = true for void methods

Turbine(Flow テスト)

Turbine は Flow テストの標準ライブラリです。test { } が制御されたスコープで収集し、awaitItem() が次のエミッションを表明し、awaitComplete()/awaitError() が終了をチェックします。タイミングと仮想時間を正しく処理します。コルーチンフレンドリーで高速、決定論的な Flow テストのために runTest と組み合わせます。

kotlin
import app.cash.turbine.test
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import kotlin.test.*
class FlowTest {
    @Test fun testFlow() = runTest {
        flowOf(1, 2, 3).test {
            assertEquals(1, awaitItem())
            assertEquals(2, awaitItem())
            assertEquals(3, awaitItem())
            awaitComplete()
        }
    }
    @Test fun testError() = runTest {
        flow<Int> { throw RuntimeException("x") }.test {
            assertTrue(awaitError() is RuntimeException)
        }
    }
}

コルーチンテスト(runTest)

runTest は仮想時間を使用します—delay が瞬時にスキップし、非同期テストを高速で決定論的にします。手動制御のために StandardTestDispatcher + advanceUntilIdle() を使用します。テスト容易性のために本番コードに TestDispatcher を注入します(Dispatchers.Main の代わり)。テストで runBlocking を避けてください—runTest がモダンな方法です。

kotlin
import kotlinx.coroutines.test.*
import kotlinx.coroutines.*
import kotlin.test.*
class RepoTest {
    @Test fun fetch() = runTest {
        // Virtual time: delay(1000) completes instantly
        val result = async { delay(1000); "data" }.await()
        assertEquals("data", result)
    }
    @Test fun withDispatcher() = runTest {
        val scheduler = StandardTestDispatcher()
        val scope = CoroutineScope(scheduler)
        // advanceUntilIdle() runs pending coroutines
        scope.launch { /*...*/ }
        scheduler.advanceUntilIdle()
    }
}

Kotest アサーションとスタイル

Kotest は表現力豊かなアサーション(shouldBe、shouldContain)と複数のスペックスタイル(StringSpec、FunSpec、BehaviorSpec for BDD)を提供します。プロパティベーステストと統合し、豊富なマッチャを持ちます。Kotlin 慣用的で読みやすいテストのために JUnit より選択してください。JUnit 5 と並行して実行できます。

kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.string.shouldStartWith
class MyTest : StringSpec({
    "addition works" {
        1 + 1 shouldBe 2
    }
    "list contains" {
        listOf(1, 2, 3) shouldContain 2
    }
    "string prefix" {
        "hello" shouldStartWith "he"
    }
})
// Other styles: FunSpec, BehaviorSpec, ShouldSpec
17

関数型プログラミングの深掘り

スコープ関数(let、run、with、apply、also)

スコープ関数はレシーバ(this vs it)と戻り値(this vs result)で異なります。apply/also はレシーバを返します(チェーン、ビルダー)、let/run/with はラムダ結果を返します(変換)。設定には apply、null チェック/変換には let、副作用には also を使用します。過剰使用しないでください—可読性第一。

kotlin
data class User(var name: String, var age: Int)
fun main() {
    val u = User("A", 1)
    // apply: configure & return this (builders)
    val configured = u.apply { age = 30 }
    // let: transform, it = receiver, returns lambda result
    val name = u.let { it.name.uppercase() }
    // run: transform, this = receiver, returns result
    val s = u.run { "$name:$age" }
    // with: like run but called as function
    val str = with(u) { "$name:$age" }
    // also: side effects, returns this (logging/chaining)
    u.also { println("Created $it") }
}

Sequence(遅延評価)

Sequence は遅延評価します—各要素が次が始まる前にパイプライン全体を流れます(Java Stream のような)。これが中間コレクションを回避し、ショートサーキットします(take/find)。大きなデータセットや多段パイプラインに使用します。小さなリストには即時 List がしばしば高速です(オーバーヘッドが少ない)。generateSequence が無限ストリームを構築します。

kotlin
fun main() {
    // Eager: List processes each step fully
    val eager = (1..10).toList()
        .map { it * 2 }
        .filter { it > 5 }
    // Lazy: Sequence processes element-by-element
    val lazy = (1..10).asSequence()
        .map { println("map $it"); it * 2 }
        .filter { println("filter $it"); it > 5 }
        .take(2)
        .toList()  // only processes until 2 found
    // Use Sequence for large/pipelined data
    // generateSequence for infinite streams
    val naturals = generateSequence(1) { it + 1 }
}

Inline 関数と reified

inline がバイトコードをインライン化してラムダオブジェクト/アロケーションオーバーヘッドを排除します—ホットループに重要。非ローカル return も可能にします(ラムダ内から外側の関数から return)。reified 型パラメータがランタイムでジェネリック型情報を利用可能にします(T::class、is T)、ただし inline が必要です。filterIsInstance のような型安全ヘルパーに使用します。

kotlin
// inline: copies bytecode at call site (no lambda overhead)
inline fun measure(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}
// non-local return: inline lambdas can return from outer fun
inline fun forEach(list: List<Int>, f: (Int) -> Unit) {
    for (i in list) f(i)
}
// reified: access generic type at runtime
inline fun <reified T> List<*>.filterIsInstance() =
    filter { it is T } as List<T>
val strs = listOf(1, "a", 2, "b").filterIsInstance<String>()

高階関数

高階関数は関数を取るまたは返します。これらが関数型 Kotlin の骨格です—map、filter、fold、compose。(Int) -> Int のような関数型は第一級です。compose がパイプラインを構築します。パターンを抽象化し、コード再利用を可能にし、宣言的コードを書くために使用します。パフォーマンスクリティカルなパスには inline をマークします。

kotlin
fun <T, R> List<T>.mapTo(transform: (T) -> R): List<R> {
    val result = mutableListOf<R>()
    for (item in this) result.add(transform(item))
    return result
}
fun compose(f: (Int) -> Int, g: (Int) -> Int): (Int) -> Int =
    { x -> f(g(x)) }
fun main() {
    val double = { x: Int -> x * 2 }
    val inc = { x: Int -> x + 1 }
    val doubleThenInc = compose(inc, double)
    println(doubleThenInc(3))  // 7
    listOf(1, 2, 3).mapTo { it * it }
}

再帰と tailrec

tailrec が末尾再帰関数(再帰が最後の操作)をループに変換し、スタックオーバーフローを防ぎます。再帰呼び出しは末尾位置になければなりません—保留中の乗算/加算は不可。関数を末尾再帰にするためにアキュムレータパラメータを使用します。大きな入力での関数型スタイルループに不可欠です。

kotlin
// Regular recursion: stack overflow on large n
fun factorial(n: Int): Long =
    if (n <= 1) 1 else n * factorial(n - 1)
// tailrec: compiler optimizes to a loop (no stack growth)
tailrec fun factorialTail(n: Int, acc: Long = 1): Long =
    if (n <= 1) acc else factorialTail(n - 1, acc * n)
// Fibonacci with tailrec
tailrec fun fib(n: Int, a: Long = 0, b: Long = 1): Long =
    when (n) { 0 -> a; 1 -> b; else -> fib(n - 1, b, a + b) }
fun main() {
    println(factorialTail(10000))  // no stack overflow
}
18

ジェネリクスと変性

ジェネリッククラスと関数

ジェネリクスが型安全で再利用可能なコードを可能にします。クラスは <T> を使用し、関数は戻り値型の前に <T> を宣言します。制約(T : Entity)が型境界を制限します。Java と異なり、Kotlin のジェネリクスは inline 関数で再化され、宣言サイト変性を持ち、ジェネリック API をより安全で人間工学的にします。

kotlin
class Box<T>(val value: T) {
    fun get(): T = value
}
fun <T> singletonList(item: T): List<T> = listOf(item)
fun main() {
    val intBox = Box(42)        // Box<Int> inferred
    val strBox = Box<String>("hi")
    val nums = singletonList(1)
}
// Generic constraints
class Repository<T : Entity> {
    fun save(item: T) { /* T is Entity subtype */ }
}
interface Entity { val id: Int }

変性(in/out)

変性がジェネリクスのサブタイプ関係を制御します。out(共変):Producer<Dog> は Producer<Animal> です—T を読むだけなので安全。in(反変):Sink<Animal> は Sink<Dog> です—T を書くだけなので安全。可変コレクションは不変です(読み + 書き)。安全で柔軟な API を設計するために out/in を使用します。

kotlin
// out (covariant): Producer<Sub> is Producer<Super>
interface Source<out T> { fun next(): T }
// in (contravariant): Consumer<Super> is Consumer<Sub>
interface Sink<in T> { fun put(item: T) }
// invariant (default): neither
class MutableList<T> {
    fun add(item: T) {}
    fun get(): T = TODO()
}
open class Animal
class Dog : Animal()
val src: Source<Animal> = Source<Dog>()  // OK (out)
val sink: Sink<Dog> = Sink<Animal>()     // OK (in)

実践的な変性

関数型は自動的に変性を使用します:パラメータは 'in'、戻り値型は 'out'。これが (Dog) -> Unit が (Animal) -> Unit に代入可能な理由です。PECS 原則(Producer Extends、Consumer Super)が Kotlin の out/in に対応します。T を生成するだけのジェネリックインターフェースには out、消費するだけには in で設計します。

kotlin
// Function types are variant by nature
// (T) -> R is contravariant in T, covariant in R
val dogHandler: (Dog) -> Unit = { println(it) }
val animalHandler: (Animal) -> Unit = dogHandler  // OK
// Covariant return types
interface Repository<out T> { fun find(id: Int): T }
class DogRepo : Repository<Dog> {
    override fun find(id: Int): Dog = Dog()
}
val repo: Repository<Animal> = DogRepo()  // OK
// PECS rule: Producer extends, Consumer super
// Kotlin: out = producer, in = consumer

型プロジェクションと Star プロジェクション

型プロジェクションが使用サイトで一時的に型を変性にします。Array<out Any> は「Any の何らかのサブタイプの配列、読み取り可能」を意味します。Array<in Any> は「Any として書き込み可能」を意味します。Star プロジェクション(*)は「ある未知の型」を意味します—サイズ/contains のみが必要で要素型は不要な場合に有用です。Java のワイルドカードのようなものです。

kotlin
fun copy(from: Array<out Any>, to: Array<in Any>) {
    // from: only read (covariant projection)
    // to: only write (contravariant projection)
    for (i in from.indices) to[i] = from[i]
}
// Star projection: unknown type, read-only
fun printSize(list: List<*>) {
    println(list.size)  // OK (no type needed)
    // list[0]  // type is Any? — limited use
}
val mixed: List<*> = listOf(1, "a", 3.0)
// Use when type is irrelevant or unknown

Reified 型パラメータ

reified がランタイムでジェネリック型情報を保持します(通常 JVM で消去)。inline が必要(各コールサイトで型が既知)。is T チェック、T::class、filterIsInstance を可能にします。reified なしでは Class<T> パラメータを手動で渡す必要があります。制限:reified 型は非 inline 関数やクラスタイプパラメータでは使用できません。

kotlin
// reified requires inline
inline fun <reified T> List<*>.filterIsInstance(): List<T> =
    filter { it is T } as List<T>
inline fun <reified T> Any.castTo(): T = this as T
inline fun <reified T> typeOf() = T::class.simpleName
fun main() {
    val list = listOf(1, "a", 2, "b", 3)
    val strs: List<String> = list.filterIsInstance()
    println(strs)  // [a, b]
    println(typeOf<String>())  // String
    // Without reified, T is erased at runtime
    // reified makes T::class and 'is T' available
}
19

Null 安全性の深掘り

Nullable 型

Kotlin はコンパイル時に nullable(T?)と非 null(T)型を区別します。?. セーフコールがレシーバが null の場合 null を返します。?: Elvis がデフォルトを提供します。!! が NPE をスローします(避けてください)。コンパイラが null チェックを強制し、Kotlin コードの NullPointerException を排除します。

kotlin
var name: String = "Alice"  // Non-null
// name = null  // Error
var nickname: String? = null  // Nullable
// Safe call
println(nickname?.length)  // null if nickname is null
// Elvis operator
val len = nickname?.length ?: 0  // 0 if null
// Not-null assertion (use sparingly)
val len2 = nickname!!.length  // NPE if null

let と run

let は値が非 null の場合ブロックを実行します(it = 値)。run はオブジェクトをレシーバとしてブロックを呼び出し、結果を返します。apply はオブジェクトを設定し、オブジェクトを返します。also は副作用を実行し、オブジェクトを返します。これらのスコープ関数が null チェックを削減し可読性を向上させます。

kotlin
val name: String? = "Alice"
name?.let {
    println("Length: ${it.length}")  // Only if not null
}
// run: object + block
val result = "Hello".run {
    length  // Returns 5
}
// apply: configure object
val list = mutableListOf<Int>().apply {
    add(1); add(2); add(3)
}

lateinit

lateinit が非 null プロパティの初期化を遅延します。プリミティブや nullable 型では使用できません。初期化前にアクセスすると UninitializedPropertyAccessException をスローします。::prop.isInitialized でチェックします。依存性注入とライフサイクル管理プロパティに有用です。

kotlin
class Service {
    lateinit var dependency: Database
    fun init() {
        dependency = connectDatabase()
    }
    fun use() {
        if (::dependency.isInitialized) {
            dependency.query()
        }
    }
}

Nullable コレクション

コレクションは nullable 要素を持てます(List<String?>)または nullable にできます(List<String>?)。filterNotNull が null を削除します。firstOrNull がスローの代わりに null を返します。nullable 要素はセーフコールで処理します。コレクション型で null 可能性を明示的にしてください。

kotlin
val list: List<String?> = listOf("a", null, "b")
val filtered = list.filterNotNull()  // ["a", "b"]
val lengths = list.map { it?.length }  // [1, null, 1]
val first = list.firstOrNull()  // "a" or null
val firstNonEmpty = list.firstOrNull { it?.isNotEmpty() == true }

プラットフォーム型

プラットフォーム型は null 可能性が不明な Java 相互運用から生じます。Kotlin はそれらに null 安全性を強制できません。Java 戻り値には常に明示的に nullable 型を宣言してください。Java で @Nullable/@NotNull アノテーションを使用します。JSR-305 アノテーションが Kotlin に null 可能性を推論させます。

kotlin
// Java interop: platform type (no null info)
val name: String = javaObject.getName()  // May be null!
// Kotlin does not know if Java returns null
// Fix: explicit nullable type
val name2: String? = javaObject.getName()
// Or @Nullable annotation in Java
20

DSL 構築

レシーバ付きラムダビルダー

レシーバ付きラムダ(T.() -> Unit)が DSL 構文を可能にします。ラムダ内で this がレシーバオブジェクトです。メソッドは修飾なしで呼び出せます。これが Kotlin が型安全 DSL(Gradle、HTML、SQL ビルダー)を構築する方法です。

kotlin
class StringBuilder {
    private val parts = mutableListOf<String>()
    fun line(s: String) { parts.add(s) }
    fun build() = parts.joinToString("\n")
}
fun buildString(init: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.init()  // Execute lambda with sb as receiver
    return sb.build()
}

HTML DSL

HTML DSL はレシーバ付きラムダでネストしたビルダーを使用します。各タグが子ビルダーを作成する関数です。ラムダが子を設定します。型安全で合成可能な HTML を生成します。kotlinx.html が実装例です。同じパターンが任意の階層構造で動作します。

kotlin
fun html(init: HTML.() -> Unit): HTML {
    val h = HTML(); h.init(); return h
}
class HTML {
    fun body(init: Body.() -> Unit) { /* ... */ }
}
html {
    body {
        // this: Body
        p("Hello")
    }
}

@DslMarker

@DslMarker が外側スコープへの暗黙のレシーバアクセスを防ぎます。これがないと、HTML と Body の両方のメソッドがアクセス可能で混乱を引き起こします。アノテーションが最も内側のレシーバへのアクセスを制限します。DSL を型安全で曖昧でないものにします。複雑な DSL に不可欠です。

kotlin
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML { fun body(...) {} }
@HtmlDsl
class Body { fun p(...) {} }
html {
    body {
        // p()  // OK: in Body scope
        // body()  // Error: in Body scope, not HTML
    }
}

Gradle DSL

Gradle Kotlin DSL は同じビルダーパターンを使用します。plugins、dependencies はレシーバラムダ付きの関数です。implementation、testImplementation は依存関係設定関数です。型安全:コンパイラが関数名とパラメータ型をチェックします。リファクタリングにおいて Groovy よりはるかに優れています。

kotlin
plugins {
    kotlin("jvm") version "1.9.0"
}
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    testImplementation(kotlin("test"))
}
// All are function calls with lambda receivers

Anko SQL

Anko(と Exposed)が型安全な SQL DSL を提供します。カラム比較は型付きです。文字列カラムを int と比較できません。DSL が SQL を生成します。SQL インジェクションと型エラーを防ぎます。同じパターンが任意のドメイン固有言語に適用されます。

kotlin
fun users(where: SqlExpressionBuilder.() -> Op<Boolean>) {
    // DSL for SQL queries
}
users {
    (Users.age greater 18) and (Users.name like "A%")
}
// Type-safe SQL: compiler checks column types
21

コレクション

List 操作

Kotlin のコレクションは豊富な関数型 API を持ちます。map、filter、reduce が標準です。groupBy がキーで分割します。chunked が固定サイズのリストに分割します。windowed がスライディングウィンドウを作成します。すべて新しいコレクションを返します。遅延評価には asSequence() を使用します。

kotlin
val list = listOf(1, 2, 3, 4, 5)
val doubled = list.map { it * 2 }
val evens = list.filter { it % 2 == 0 }
val sum = list.reduce { acc, n -> acc + n }
val grouped = list.groupBy { it % 2 }  // {0=[2,4], 1=[1,3,5]}
val chunked = list.chunked(2)  // [[1,2],[3,4],[5]]

Sequence

Sequence は遅延コレクションです(Java Stream のような)。操作はオンデマンドで評価されます。中間コレクションはありません。チェーン操作のある大きなコレクションに効率的です。変換には asSequence() を使用します。toList()、toSet() などで強制します。

kotlin
val result = (1..1000000).asSequence()
    .map { it * 2 }
    .filter { it > 100 }
    .take(10)
    .toList()
// Lazy: only computes what is needed
// No intermediate collections

分解

分解がオブジェクトを変数に分割します。Pair、Triple、data class、Map エントリで動作します。componentN() 関数がこれを可能にします。withIndex() がインデックスと値をペアにします。複数戻り値と反復に有用です。Data class が componentN を自動生成します。

kotlin
val (a, b) = Pair(1, "hello")
val (x, y, z) = Triple(1, 2, 3)
for ((index, value) in list.withIndex()) {
    println("$index: $value")
}
data class Point(val x: Int, val y: Int)
val (px, py) = Point(1, 2)

可変 vs 不変

Kotlin には可変(MutableList)と不変(List)コレクションの両方があります。安全性のために不変を優先します。toList() が不変コピーを作成します。toMutableList() が可変コピーを作成します。不変インターフェースは変更メソッドを公開せず、偶発的な変更を防ぎます。

kotlin
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)  // OK
val immutable = listOf(1, 2, 3)
// immutable.add(4)  // Error: no add method
// Convert
val imm = mutable.toList()
val mut = imm.toMutableList()

Associate と Partition

toMap/associate がマップに変換します。partition が述語で2つのリストに分割します。flatten がネストしたリストをマージします。flatMap がマップしてフラット化します。これらが冗長なループを宣言的式で置き換えます。すべて新しいコレクションを返します。

kotlin
val list = listOf("a" to 1, "b" to 2)
val map = list.toMap()  // {a=1, b=2}
val byLength = list.associate { it.first to it.second }
val (evens, odds) = list.partition { it.second % 2 == 0 }
// evens: [(a,1)?] odds: [(b,2)]
val flat = listOf(listOf(1), listOf(2, 3)).flatten()  // [1,2,3]
22

一般的な落とし穴

== vs ===

Kotlin の == は equals を呼び出します(値等価)、Java と異なります。=== が参照等価をチェックします。値比較には == を使用します。=== はほとんど必要ありません。Integer の場合、-128 から 127 はキャッシュされるため、=== は true または false になります。値には常に == を使用します。

kotlin
val a = Integer(127)
val b = Integer(127)
a == b  // true (value equality, calls equals)
a === b  // true (Integer caches -128 to 127)
val c = Integer(128)
val d = Integer(128)
c == d  // true
c === d  // false (not cached)

Companion Object

Kotlin には静的メンバーがありません。companion object が「静的」メソッドと定数を保持します。const val が真のコンパイル時定数です。companion object はシングルトンインスタンスです。@JvmStatic がメソッドを Java から静的として呼び出し可能にします。真の静的にはトップレベル関数を使用します。

kotlin
class MyClass {
    companion object {
        const val CONSTANT = 42
        fun create() = MyClass()
    }
}
MyClass.CONSTANT  // 42
MyClass.create()  // Static-like access
// companion object is a real object, not static

Data Class Copy

Data class が copy() を自動生成し、変更されたコピーを作成します。元は変更されません(不変)。指定されたフィールドのみ変更されます。更新に有用です。分解と組み合わせて、data class はデータモデリングに強力です。不変性のために data class で var を避けてください。

kotlin
data class User(val name: String, val age: Int)
val alice = User("Alice", 30)
val older = alice.copy(age = 31)  // New instance
// alice is unchanged
// copy uses named arguments for changed fields

Sealed Class の網羅性

Sealed class はサブタイプを同じファイル/パッケージに制限します。sealed class の when は網羅的です:コンパイラがブランチの欠落で警告します。すべてのケースがカバーされていれば else は不要です。新しいサブタイプを追加するとすべての場所で警告を引き起こします。状態機械と結果に理想的です。

kotlin
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
fun handle(r: Result) = when (r) {
    is Success -> r.value
    // Warning: Missing Failure branch
}
// Fix: handle all cases
fun handle(r: Result) = when (r) {
    is Success -> r.value
    is Failure -> 0
}  // No else needed

拡張 vs メンバー

メンバー関数は常に同じシグネチャの拡張関数より優先されます。拡張はコンパイル時に解決され(静的ディスパッチ)、メンバーはランタイムで(動的ディスパッチ)。拡張はメンバーをオーバーライドできません。ユーティリティ関数に拡張を使用し、多相には使用しません。

kotlin
class Foo {
    fun bar() = "member"  // Member function
}
fun Foo.bar() = "extension"  // Extension function
Foo().bar()  // "member" - members win!
// Extensions are resolved statically
// Members are resolved dynamically (virtual)
23

Kotlin Multiplatform

共通コード

Kotlin Multiplatform(KMP)はプラットフォーム間でコードを共有します。expect/actual がプラットフォーム固有の実装を宣言します。commonMain が共有コードを持ちます。プラットフォーム固有のソースセットが actual を実装します。ビジネスロジックを共有し、UI はネイティブに保ちます。Gradle がターゲットを設定します。

kotlin
// commonMain/src/Platform.kt
expect fun getPlatformName(): String
// androidMain/src/Platform.kt
actual fun getPlatformName(): String = "Android"
// iosMain/src/Platform.kt
actual fun getPlatformName(): String = "iOS"
// Shared business logic
class Greeting {
    fun greet() = "Hello from ${getPlatformName()}"
}

共有モジュール

Multiplatform プロジェクトは Gradle で kotlin ブロックを使用します。ターゲット(android、ios)を定義します。commonMain が共有依存関係を持ちます。プラットフォームソースセットはプラットフォーム固有の依存関係を持てます。iOS は直接コンパイルのために Kotlin/Native を使用します。ロジックを共有し、UI は共有しません。

kotlin
// build.gradle.kts (shared module)
kotlin {
    androidTarget()
    iosX64(); iosArm64(); iosSimulatorArm64()
    sourceSets {
        val commonMain by getting {
            dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") }
        }
    }
}

ネットワーキング(Ktor)

Ktor はマルチプラットフォーム HTTP クライアントです。API は共通、エンジンはプラットフォーム固有です。Android は OkHttp/Android エンジンを使用します。iOS は Darwin を使用します。コルーチンはプラットフォーム間で動作します。ネットワーキング、データベース(SQLDelight)、ビジネスロジックを共有します。

kotlin
// Shared HTTP client
suspend fun fetchUser(): User {
    return client.get("https://api.example.com/user").body()
}
// Platform-specific engine
// Android: OkHttp or Android
// iOS: Darwin
// Common code uses the same API

SQLDelight

SQLDelight が SQL から型安全な Kotlin を生成します。.sq ファイルに名前付きクエリ付きの SQL を含みます。型安全なクエリオブジェクトを生成します。プラットフォーム間で動作します(Android、iOS、JVM)。SQL が信頼できるソースです。スキーママイグレーションが追跡されます。マルチプラットフォーム用の Room の代替です。

kotlin
// .sq file: User.sq
CREATE TABLE User (id INTEGER, name TEXT);
selectById: SELECT * FROM User WHERE id = ?;
insertUser: INSERT INTO User (id, name) VALUES (?, ?);
// Generated Kotlin code
val queries: UserQueries = database.userQueries
queries.insertUser(1, "Alice")
val user = queries.selectById(1).executeAsOne()

Compose Multiplatform

Compose Multiplatform が Jetpack Compose を iOS、Desktop、Web に拡張します。プラットフォーム間で UI コードを共有します。同じ @Composable API。プラットフォーム固有のエントリポイント。iOS ではまだ実験的です。UI の重複を削減します。Gradle プラグイン:compose-multiplatform。

kotlin
// Shared UI with Compose Multiplatform
@Composable
fun Greeting(name: String) {
    Text("Hello, $name!")
}
// Android
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { Greeting("World") }
    }
}
// Desktop
fun main() = application {
    Window { Greeting("Desktop") }
}
24

Kotlin テスト

JUnit 5

JUnit 5 は標準テストフレームワークです。Kotlin は可読性のためにバッククォートテスト名を許可します。assertEquals、assertThrows が一般的なアサーションです。@BeforeEach、@AfterEach がセットアップ/ティアダウン用。データ駆動テストに @ParameterizedTest。マルチプラットフォームには kotlin.test を使用します。

kotlin
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
class CalculatorTest {
    @Test
    fun `test addition`() {
        val calc = Calculator()
        assertEquals(5, calc.add(2, 3))
    }
    @Test
    fun `throws on negative`() {
        assertThrows<IllegalArgumentException> { calc.sqrt(-1) }
    }
}

MockK

MockK は Kotlin ネイティブのモックライブラリです。final class と拡張関数をサポートします。every がスタブし、verify が呼び出しをチェックします。@MockK がモックを作成し、@InjectMockKs が注入します。コルーチンには coEvery/coVerify。Mockito より優れた Kotlin サポート。

kotlin
class UserServiceTest {
    @MockK
    lateinit var repo: UserRepository
    @InjectMockKs
    lateinit var service: UserService
    @BeforeEach
    fun setup() {
        MockKAnnotations.init(this)
        every { repo.find(1) } returns User("Alice")
    }
    @Test
    fun `test find`() {
        assertEquals("Alice", service.find(1).name)
        verify { repo.find(1) }
    }
}

Kotest

Kotest は Kotlin ファーストのテストフレームワークです。複数スタイル:StringSpec、BehaviorSpec、FunSpec。shouldBe が流暢なアサーション。Arb でプロパティテスト。データ駆動テストをサポート。Spring と Ktor と統合。JUnit より Kotlin 慣用的。

kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
class CalculatorTest : StringSpec({
    "addition should work" {
        Calculator().add(2, 3) shouldBe 5
    }
    "should throw on negative" {
        shouldThrow<IllegalArgumentException> { Calculator().sqrt(-1) }
    }
})

コルーチンテスト

kotlinx-coroutines-test の runTest が仮想時間を提供します。delay が瞬時にスキップします。advanceUntilIdle が保留中のコルーチンを実行します。実時間よりはるかに高速。細かい制御に TestDispatcher を使用します。Turbine ライブラリが Flow エミッションをテストします。

kotlin
@Test
fun `test async`() = runTest {
    val result = fetchData()
    assertEquals("data", result)
}
// runTest replaces runBlocking with virtual time
// advanceUntilIdle() runs all pending coroutines
// Delay skips instantly

Turbine(Flow テスト)

Turbine が Flow エミッションをテストします。awaitItem が次のエミッションを取得します。awaitComplete が完了を表明します。awaitError がエラーを表明します。test ブロックは flow が完了するまでサスペンドします。手動収集よりはるかにクリーン。Flow テストに不可欠です。

kotlin
@Test
fun `test flow`() = runTest {
    flowOf(1, 2, 3).test {
        awaitItem() shouldBe 1
        awaitItem() shouldBe 2
        awaitItem() shouldBe 3
        awaitComplete()
    }
}
// Test errors
flow<Int> { throw Exception() }.test {
    awaitError() shouldBe Exception()
}
25

コルーチンの深掘り

コルーチンスコープ

CoroutineScope がコルーチンのライフタイムを定義します。viewModelScope は VM クリア時に自動キャンセルします。SupervisorJob が子の失敗が兄弟をキャンセルするのを防ぎます。カスタムスコープは明示的なキャンセルが必要です。スコープはキャンセルを子に伝播します。本番で GlobalScope を使用しないでください(キャンセル不可)。

kotlin
class MyViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val data = api.getData()
            _data.value = data
        }
    }
}
// Custom scope
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch { /* ... */ }
scope.cancel()  // Cancel all children

Dispatcher

Dispatcher がコルーチンをスレッドプールにルーティングします。Main: UI スレッド(Android)。IO: ブロッキング I/O(64+ スレッド)。Default: CPU 作業(CPU 数スレッド)。Unconfined: 呼び出し元スレッド(高度)。withContext が dispatcher を一時的に切り替えます。適切な dispatcher の選択がパフォーマンスを向上させ UI フリーズを防ぎます。

kotlin
launch(Dispatchers.Main) { updateUI() }
launch(Dispatchers.IO) { writeFile() }
launch(Dispatchers.Default) { heavyComputation() }
launch(Dispatchers.Unconfined) { runAnywhere() }
// Switch context
withContext(Dispatchers.IO) {
    val data = readDisk()
}

Flow オペレータ

Flow オペレータ:map が変換、filter が選択、flatMapMerge/flatMapConcat が flow をチェーン。コールド flow はコレクタごとに新しく開始します。StateFlow が値を保持します(LiveData のような)。SharedFlow が複数コレクタにブロードキャストします。stateIn がコールドをホットに変換します。buffer/conflate がバックプレッシャーを制御します。

kotlin
flowOf(1, 2, 3, 4, 5)
    .map { it * 2 }
    .filter { it > 4 }
    .collect { println(it) }  // 6, 8, 10

// Cold flow: emits per collector
// StateFlow: hot, stateful
// SharedFlow: hot, broadcast
val state = MutableStateFlow(0)
state.value = 1

例外処理

CoroutineExceptionHandler が launch の未キャッチ例外をキャッチします。async の例外は await に伝播します。SupervisorJob が子の失敗を分離します。CancellationException は特別です:再スローされ、catch されません。CancellationException を絶対に飲み込まないでください。クリーンアップには try/finally または use() を使用します。キャンセルは suspend 呼び出しを通じて伝播します。

kotlin
val handler = CoroutineExceptionHandler { _, e ->
    Log.e("TAG", "Caught: $e", e)
}
scope.launch(handler) {
    throw RuntimeException("oops")
}
// try/catch in coroutine
try {
    riskyCall()
} catch (e: Exception) {
    // handle
}

チャネル

Channel がコルーチン間通信を可能にします。満杯で send がサスペンドし、空で receive がサスペンドします。capacity: バッファ付き(RENDEZVOUS=0、UNLIMITED、CONFLATED)。close() が完了をシグナルします。produce がプロデューサコルーチンを作成します。Channel はホットです:値は一度消費されます。ほとんどのユースケースには Flow を優先します。

kotlin
val channel = Channel<Int>(capacity = 10)
launch {
    for (i in 1..5) channel.send(i)
    channel.close()
}
launch {
    for (x in channel) println(x)
}
// Produce pattern
fun numbers() = produce {
    for (i in 1..10) send(i)
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.