Kotlin 言語仕様ガイド

Java の経験者が 2〜3 時間でざっと読み通せるリファレンスです。null 安全・拡張関数・データクラス・コルーチンなど Kotlin 固有の機能を重点的に解説します。

Kotlin 2.x経験者向けコード例付き
01

変数・型・null 安全

基礎
val / var と型推論基礎

val は再代入不可(Java の final に相当)、var は再代入可。型は右辺から推論されるため省略できる。

Kotlin
val name: String = "Kotlin"  // 明示的型指定
val version = 2              // 推論 → Int
var count = 0
count = 1                    // OK
// name = "Java"            // コンパイルエラー(val は再代入不可)
null 安全基礎

型に ? を付けると nullable になる。?.(safe call)、?:(Elvis 演算子)、!!(non-null assertion)を使い null を安全に扱う。

Kotlin
var s: String? = null      // nullable
val len  = s?.length         // null → null(NPE 発生せず)
val len2 = s?.length ?: 0   // null → 0
val len3 = s!!.length       // null なら NullPointerException
!! は null でないことが確実な場合のみ使用すること。乱用すると NPE を招く。
スマートキャスト基礎

is で型チェックした後、コンパイラが自動的にその型へキャストする。明示的な as キャストが不要になる。

Kotlin
fun describe(obj: Any) {
    if (obj is String) {
        println(obj.length)   // obj は自動的に String にキャスト
    }
    if (obj !is Int) return
    println(obj + 1)          // obj は Int にキャスト済み
}
基本型(プリミティブなし)基礎

Kotlin に Java のような primitive 型はなく、すべてクラス(IntLongDoubleBoolean 等)として扱う。JVM バイトコードでは可能な限りプリミティブに最適化される。

Kotlin
val i: Int     = 42
val l: Long    = 100L
val d: Double  = 3.14
val b: Boolean = true
val c: Char    = 'A'
// 数値リテラルの区切り
val million = 1_000_000
02

制御フロー

基礎
if 式基礎

Kotlin の if は**式**(値を返す)。三項演算子 ? : の代わりに使える。

Kotlin
val max = if (a > b) a else b

val grade = if (score >= 90) "A"
            else if (score >= 70) "B"
            else "C"
when 式基礎

when は Java の switch の強化版。値だけでなく、型チェック・範囲・条件式などを分岐できる。式として値を返せる。

Kotlin
val result = when (x) {
    0        -> "zero"
    in 1..9  -> "single digit"
    is String -> "string"
    else     -> "other"
}

// 引数なし when
when {
    x < 0  -> println("negative")
    x == 0 -> println("zero")
    else   -> println("positive")
}
for と while基礎

forin でコレクションや範囲を反復。.. で閉区間、until で半開区間、step でステップ幅を指定できる。

Kotlin
for (i in 1..5) print(i)               // 1 2 3 4 5
for (i in 1 until 5) print(i)          // 1 2 3 4
for (i in 5 downTo 1 step 2) print(i) // 5 3 1

val list = listOf("a", "b", "c")
for ((index, value) in list.withIndex()) {
    println("${index}: ${value}")
}

var n = 3
while (n > 0) n--
ラベル付き return基礎

ラムダ内から外側の関数へ return するには return@ラベル名 を使う。breakcontinue も同様にラベルで対象ループを指定できる。

Kotlin
fun findFirst(list: List<Int>, target: Int) {
    list.forEach { n ->
        if (n == target) return@forEach  // ラムダから抜ける(次要素へ)
        println(n)
    }
}

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) break@outer  // 外側 for を break
    }
}
03

関数

基礎
デフォルト引数・名前付き引数基礎

引数にデフォルト値を設定でき、Java のようなオーバーロードを減らせる。呼び出し時は引数名を指定して渡せる(順序不問)。

Kotlin
fun greet(name: String, greeting: String = "Hello") =
    "${greeting}, ${name}!"

println(greet("Alice"))                          // Hello, Alice!
println(greet("Bob", "Hi"))                      // Hi, Bob!
println(greet(greeting = "Hey", name = "Carol")) // Hey, Carol!
単一式関数基礎

関数本体が一つの式の場合、= 式 の形で書ける。戻り値型も推論される。

Kotlin
fun double(x: Int): Int = x * 2

fun max(a: Int, b: Int) = if (a > b) a else b  // 戻り値型を推論

fun isEven(n: Int) = n % 2 == 0
infix 関数基礎

infix を付けた単一引数のメンバー関数または拡張関数は、中置記法(スペース区切り)で呼べる。mapOfto などが好例。

Kotlin
infix fun Int.pow(exp: Int): Int =
    Math.pow(toDouble(), exp.toDouble()).toInt()

val result = 2 pow 10   // 1024(2.pow(10) と同等)

// 標準ライブラリの infix 例
val pair = "key" to "value"  // Pair<String, String>
tailrec 関数基礎

tailrec を付けると末尾再帰をループに最適化してくれる。スタックオーバーフローを防ぎつつ再帰的な記述ができる。

Kotlin
tailrec fun factorial(n: Long, acc: Long = 1): Long =
    if (n <= 1) acc else factorial(n - 1, acc * n)

println(factorial(100_000))  // スタックオーバーフローなし
04

クラスとオブジェクト

基礎
data class基礎

data classequalshashCodetoStringcopycomponentN を自動生成。イミュータブルな値オブジェクトに最適。

Kotlin
data class Point(val x: Int, val y: Int)

val p1 = Point(1, 2)
val p2 = p1.copy(y = 3)  // Point(1, 3)
println(p1 == p2)         // false(値比較)

val (x, y) = p1           // 構造分解宣言
object と companion object基礎

object 宣言でシングルトンを作成。companion object はクラス内に定義し、Java の static メンバーに相当する。

Kotlin
object AppConfig {
    val version = "1.0"
    fun reset() { /* ... */ }
}

class MyClass {
    companion object {
        const val TAG = "MyClass"
        fun create() = MyClass()
    }
}

AppConfig.version
MyClass.TAG
MyClass.create()
sealed class基礎

sealed class はサブクラスを同一ファイル(または同一パッケージ)に限定する。when と組み合わせると網羅チェックが効いて安全な分岐が書ける。

Kotlin
sealed class Result<out T> {
    data class Success<T>(val data: T)       : Result<T>()
    data class Failure(val error: Throwable) : Result<Nothing>()
    object Loading                           : Result<Nothing>()
}

fun handle(r: Result<String>) = when (r) {
    is Result.Success -> println(r.data)
    is Result.Failure -> println(r.error)
    Result.Loading    -> println("loading...")
    // else 不要(網羅済み)
}
コンストラクタ(primary / secondary)基礎

クラスヘッダーに書く primary コンストラクタと、本体内に定義する secondary コンストラクタがある。init ブロックで初期化ロジックを記述できる。

Kotlin
class Person(val name: String, var age: Int) {  // primary
    init {
        require(age >= 0) { "age must be >= 0" }
    }
    constructor(name: String) : this(name, 0)   // secondary
}
05

拡張関数・プロパティ

基礎
拡張関数基礎

既存クラスのソースを変更せずにメソッドを追加できる。receiver型.関数名 で定義し、this でレシーバーを参照する。

Kotlin
fun String.isPalindrome(): Boolean = this == this.reversed()

println("racecar".isPalindrome())  // true
println("kotlin".isPalindrome())   // false

fun Int.isEven() = this % 2 == 0
println(4.isEven())  // true
拡張プロパティ基礎

既存クラスにプロパティを追加できる。バッキングフィールドを持てないため get() と必要なら set() を実装する。

Kotlin
val String.wordCount: Int
    get() = trim().split("\s+".toRegex()).size

println("hello world foo".wordCount)  // 3
スコープ関数基礎

letrunapplyalsowith はラムダをレシーバーや引数として渡す高階関数。オブジェクトの設定・変換・null チェックを簡潔に書ける。

Kotlin
// let: null チェック+変換(it でアクセス、返り値=ラムダ結果)
val len = str?.let { it.length }

// apply: レシーバー返却、初期化に便利(this でアクセス)
val sb = StringBuilder().apply {
    append("Hello")
    append(", World")
}

// also: 副作用(ログ等)、オブジェクト自体を返す
val nums = mutableListOf(1, 2, 3).also { println("list: $it") }

// run: 結果を返す(this でアクセス)
val result = "  kotlin  ".run { trim().uppercase() }

// with: 拡張関数ではない(引数でオブジェクトを渡す)
val s = with(StringBuilder()) { append("a"); append("b"); toString() }
拡張関数の制限基礎

拡張関数はコンパイル時に静的ディスパッチされる。サブクラスで override はできず、メンバー関数と同名の場合はメンバー関数が優先される。

Kotlin
open class Animal
class Dog : Animal()

fun Animal.speak() = "..."
fun Dog.speak() = "Woof"

val d: Animal = Dog()
println(d.speak())  // "..."(静的ディスパッチ:宣言型 Animal で解決)
拡張関数はポリモーフィズムを持たない。動的ディスパッチが必要な場合はメンバー関数を使うこと。
06

インターフェース・継承

基礎
interface基礎

Kotlin の interface はデフォルト実装(メソッド本体)を持てる。プロパティも宣言でき、実装クラスで override する。

Kotlin
interface Greeter {
    val greeting: String
    fun greet(name: String) = "${greeting}, ${name}!"  // デフォルト実装
}

class FormalGreeter : Greeter {
    override val greeting = "Good day"
}

println(FormalGreeter().greet("Alice"))  // Good day, Alice!
open クラスと override基礎

Kotlin のクラスはデフォルト final。継承を許可するには open を付ける。メソッドも同様に open が必要。override したメンバーも暗黙的に open になる。

Kotlin
open class Shape {
    open fun area(): Double = 0.0
}

class Circle(val radius: Double) : Shape() {
    override fun area() = Math.PI * radius * radius
}

// さらに継承させたくない場合
// final override fun area() = ...
abstract クラス基礎

abstract クラスはインスタンス化不可。抽象メンバーは実装クラスで必ず override する。インターフェースと異なりフィールドを持てる。

Kotlin
abstract class Animal(val name: String) {
    abstract fun sound(): String
    fun describe() = "${name} says ${sound()}"
}

class Cat(name: String) : Animal(name) {
    override fun sound() = "Meow"
}

println(Cat("Whiskers").describe())  // Whiskers says Meow
委譲(by キーワード)基礎

by キーワードでインターフェース実装を別のオブジェクトに委譲できる。コンポジションをボイラープレートなしに記述できる。

Kotlin
interface Logger {
    fun log(msg: String)
}

class ConsoleLogger : Logger {
    override fun log(msg: String) = println("[LOG] ${msg}")
}

class Service(logger: Logger) : Logger by logger {
    fun run() { log("service started") }
}

Service(ConsoleLogger()).run()  // [LOG] service started
07

ジェネリクス

基礎
型パラメータと制約基礎

で型パラメータに上限制約を設ける。複数制約は where 節で記述する。

Kotlin
fun <T : Comparable<T>> max(a: T, b: T): T = if (a > b) a else b

// 複数制約
fun <T> process(item: T) where T : Serializable, T : Comparable<T> {
    // ...
}

println(max(3, 7))             // 7
println(max("apple", "banana")) // banana
reified 型パラメータ基礎

inline fun の型パラメータに reified を付けると、実行時に型情報を保持できる。is T チェックや T::class が使えるようになる。

Kotlin
inline fun <reified T> filterByType(list: List<Any>): List<T> =
    list.filterIsInstance<T>()

inline fun <reified T> Any.isType() = this is T

val mixed = listOf(1, "a", 2, "b")
println(filterByType<String>(mixed))  // [a, b]
println(42.isType<Int>())             // true
変性(out / in)基礎

Kotlin は宣言サイト変性をサポート。out T(共変)は読み取り専用、in T(反変)は書き込み専用を表す。

Kotlin
// out: covariant — Producer<Dog> は Producer<Animal> として使える
interface Producer<out T> { fun produce(): T }

// in: contravariant — Consumer<Animal> は Consumer<Dog> として使える
interface Consumer<in T> { fun consume(item: T) }

// 使用サイト変性(プロジェクション)
fun copy(from: Array<out Any>, to: Array<Any>) {
    from.forEachIndexed { i, v -> to[i] = v }
}
08

ラムダ・高階関数

基礎
ラムダ式とクロージャ基礎

ラムダは { 引数 -> 本体 } で記述。外側スコープの変数をキャプチャ(クロージャ)できる。最後の引数がラムダなら括弧の外に出せる(trailing lambda)。

Kotlin
val square: (Int) -> Int = { x -> x * x }
println(square(5))  // 25

// trailing lambda
listOf(1, 2, 3).forEach { println(it) }

// クロージャ
var counter = 0
val increment = { counter++ }
increment(); increment()
println(counter)  // 2
高階関数基礎

関数を引数や戻り値として扱う高階関数。filtermapreducefold などが標準ライブラリに豊富に揃っている。

Kotlin
val nums = listOf(1, 2, 3, 4, 5)
val evens   = nums.filter { it % 2 == 0 }       // [2, 4]
val doubled = nums.map { it * 2 }               // [2, 4, 6, 8, 10]
val sum     = nums.reduce { acc, v -> acc + v } // 15
val product = nums.fold(1) { acc, v -> acc * v } // 120
it 省略形基礎

引数が一つのラムダは it で参照できる。ネストする場合は名前をつけて明示した方が読みやすい。

Kotlin
listOf("a", "bb", "ccc").filter { it.length > 1 }  // [bb, ccc]

// ネスト時は it を使わず名前をつける
listOf(listOf(1, 2), listOf(3, 4)).flatMap { inner ->
    inner.map { num -> num * 2 }
}
inline 関数基礎

inline fun はコンパイル時にラムダを呼び出し元にインライン展開し、関数オブジェクトの生成コストを削減する。

Kotlin
inline fun measure(block: () -> Unit): Long {
    val start = System.nanoTime()
    block()
    return System.nanoTime() - start
}

val ns = measure { repeat(1_000_000) { /* work */ } }
println("${ns}ns")  // ラムダオブジェクト生成なし
09

コレクション

基礎
Mutable vs Immutable コレクション基礎

Kotlin はコレクションを読み取り専用(listOf)と変更可能(mutableListOf)に分けて扱う。インターフェースレベルで区別されている。

Kotlin
val immutable = listOf(1, 2, 3)
// immutable.add(4)  // コンパイルエラー

val mutable = mutableListOf(1, 2, 3)
mutable.add(4)

val map = mapOf("a" to 1, "b" to 2)
val mutableMap = mutableMapOf("a" to 1)
mutableMap["c"] = 3
Sequence(遅延評価)基礎

Sequence は中間処理を遅延評価するため、大量データや無限列に効率的。asSequence() で変換できる。

Kotlin
// List: filter と map がそれぞれ全要素を処理(中間コレクション生成)
(1..1_000_000).toList().filter { it % 2 == 0 }.map { it * 2 }.take(5)

// Sequence: 要素ごとに filter→map→take を通過(中間コレクション不要)
(1..1_000_000).asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .toList()  // terminal: ここで初めて実行
groupBy・associate・partition・zip基礎

コレクションをグループ化・マップ変換・分割・結合する標準関数。

Kotlin
val words = listOf("apple", "banana", "apricot", "blueberry")

val byFirst = words.groupBy { it[0] }
// {a=[apple, apricot], b=[banana, blueberry]}

val lengths = words.associate { it to it.length }
// {apple=5, banana=6, ...}

val (aWords, bWords) = words.partition { it.startsWith("a") }

val zipped = listOf(1, 2, 3).zip(listOf("a", "b", "c"))
// [(1, a), (2, b), (3, c)]
破壊的宣言(構造分解)基礎

data classPairMap.EntrycomponentN() 関数により変数の構造分解ができる。

Kotlin
val (x, y) = Pair(10, 20)
println("${x}, ${y}")  // 10, 20

data class Color(val r: Int, val g: Int, val b: Int)
val (r, g, b) = Color(255, 128, 0)

// Map エントリーを分解
for ((key, value) in mapOf("a" to 1, "b" to 2)) {
    println("${key}=${value}")
}
10

標準ライブラリ・文字列

基礎
文字列テンプレート基礎

バックティック文字列ではなく通常の文字列内で ${式} を使って値を埋め込める。単純な変数は $変数名 でも書ける。

Kotlin
val name = "Kotlin"
val version = 2
println("Hello, ${name} ${version}!")   // Hello, Kotlin 2!
println("Length: ${name.length}")        // Length: 6
println("Is $name great? ${true}")      // Is Kotlin great? true
raw 文字列基礎

"""...""" で raw 文字列(エスケープ不要)を書ける。trimIndent()trimMargin() で共通インデントを除去できる。

Kotlin
val json = """
    {
        "name": "Kotlin",
        "version": 2
    }
""".trimIndent()

val text = """
    |Line 1
    |Line 2
""".trimMargin()  // | をマージンプレフィックスとして除去
buildString と StringBuilder基礎

buildString { }StringBuilder を用いた文字列構築の DSL。可読性が高く、+ による文字列連結より効率的。

Kotlin
val result = buildString {
    append("Hello")
    append(", ")
    append("World")
    appendLine("!")
    repeat(3) { append("*") }
}
println(result)
// Hello, World!
// ***
便利な文字列操作基礎

Kotlin は文字列を操作する拡張関数を豊富に提供している。

Kotlin
val s = "  Hello, Kotlin!  "
println(s.trim())                         // "Hello, Kotlin!"
println(s.trim().lowercase())             // "hello, kotlin!"
println("abc".padStart(5, '0'))           // "00abc"
println("a,b,c".split(","))               // [a, b, c]
println("Hello".repeat(3))               // HelloHelloHello
println("kotlin".substringBefore("lin")) // "kot"
11

ファイル・IO

基礎
File.readText / writeText / forEachLine基礎

java.io.File に対する Kotlin 拡張関数で手軽にファイル I/O ができる。

Kotlin
import java.io.File

val content = File("input.txt").readText(Charsets.UTF_8)
File("output.txt").writeText("Hello, Kotlin!")
File("data.txt").forEachLine { line ->
    println(line)
}
val lines: List<String> = File("data.txt").readLines()
use(AutoCloseable)基礎

use { } は Java の try-with-resources に相当。ブロック終了時(例外発生時も)に close() が自動で呼ばれる。

Kotlin
import java.io.BufferedReader
import java.io.FileReader

BufferedReader(FileReader("input.txt")).use { reader ->
    reader.lineSequence().forEach { println(it) }
}  // ここで reader.close() が自動呼び出し
kotlin.io 拡張関数基礎

kotlin.io パッケージはファイル操作の拡張関数を多数提供する。

Kotlin
import java.io.File

// ファイルコピー
File("src.txt").copyTo(File("dst.txt"), overwrite = true)

// ディレクトリ再帰削除
File("tmp_dir").deleteRecursively()

// ディレクトリウォーク
File("src").walk()
    .filter { it.extension == "kt" }
    .forEach { println(it.path) }
12

型システム・キャスト

基礎
as と as?(キャスト)基礎

as は unsafe キャスト(失敗で ClassCastException)、as? は safe キャスト(失敗で null)。

Kotlin
val obj: Any = "Hello"
val s1: String  = obj as String          // OK
val n1: Int?    = obj as? Int            // null(ClassCastException なし)
val s2: String  = (obj as? String) ?: "fallback"
Any・Nothing・Unit基礎

Any はすべての型のスーパー型(Java の Object)。Unit は値を返さない関数の戻り値型(Java の void)。Nothing は「決して値を返さない」を表す(例外を投げる関数など)。

Kotlin
fun log(msg: Any) = println(msg)   // Any は何でも受け取れる

fun fail(msg: String): Nothing =
    throw IllegalStateException(msg)  // Nothing: 値が返らない

fun doWork(): Unit {                  // Unit = void、省略可
    println("working")
    // return Unit は暗黙
}
typealias基礎

typealias で既存の型に別名を付けられる。複雑な関数型やジェネリクスを簡潔に表現するのに便利。

Kotlin
typealias StringList   = List<String>
typealias ClickHandler = (x: Int, y: Int) -> Unit

fun processNames(names: StringList) { /* ... */ }
val onClick: ClickHandler = { x, y -> println("clicked $x, $y") }
value class(inline class)基礎

@JvmInline value class は単一プロパティをラップするが、JVM ではボクシングを排除して効率的に動作する。型安全なラッパーに使う。

Kotlin
@JvmInline
value class UserId(val id: Long)

@JvmInline
value class Email(val address: String)

fun sendEmail(to: Email, from: Email) { /* ... */ }

// コンパイル後は Long として展開され、オブジェクト生成なし
sendEmail(Email("a@example.com"), Email("b@example.com"))
13

コルーチン基礎

応用
suspend 関数応用

suspend 関数はコルーチン内でのみ呼び出せる中断可能な関数。スレッドをブロックせず、他のコルーチンに処理を譲れる。

Kotlin
import kotlinx.coroutines.*

suspend fun fetchData(): String {
    delay(1000)        // スレッドをブロックせず 1 秒待機
    return "data"
}

fun main() = runBlocking {
    val result = fetchData()
    println(result)    // data
}
launch と async / await応用

launch は結果を返さない(Job)、async は結果を返す(Deferred)コルーチンビルダー。await() で結果を取得する。

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    // launch: fire-and-forget
    val job = launch {
        delay(500)
        println("launched!")
    }

    // async: 結果を返す
    val deferred = async {
        delay(300)
        42
    }

    println("result: ${deferred.await()}")  // result: 42
    job.join()
}
Dispatchers応用

Dispatchers はコルーチンが実行されるスレッドプールを指定する。IO(I/O 処理)・Default(CPU 集中)・Main(UI スレッド)などがある。

Kotlin
import kotlinx.coroutines.*
import java.io.File

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        val data = File("large.txt").readText()  // I/O スレッドで実行
        withContext(Dispatchers.Default) {
            // CPU 負荷処理(計算用スレッドプール)
            processData(data)
        }
    }
}
構造化並行処理応用

コルーチンは親スコープの生存期間に縛られる。親がキャンセルされると子もキャンセルされる。これによりリークのない並行処理が書ける。

Kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val scope = CoroutineScope(Dispatchers.Default)
    val job = scope.launch {
        repeat(10) { i ->
            delay(100)
            println("tick ${i}")
        }
    }
    delay(350)
    scope.cancel()    // scope 内のすべてのコルーチンをキャンセル
    println("cancelled")
}
14

Flow

応用
Flow<T> の基本応用

Flow は非同期で複数の値を順次生成するコールドストリーム。flow { } ビルダーで定義し、collect { } で受け取る。

Kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun numberFlow(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)          // 値を下流へ流す
    }
}

fun main() = runBlocking {
    numberFlow()
        .map { it * 2 }
        .filter { it > 4 }
        .collect { value -> println(value) }  // 6 8 10
}
StateFlow と SharedFlow応用

StateFlow は最新値を保持するホットストリーム(Android ViewModel 状態管理に最適)。SharedFlow は複数コレクターへのブロードキャストに使う。

Kotlin
import kotlinx.coroutines.flow.*

// StateFlow: 常に現在値を保持
class CounterViewModel {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    fun increment() { _count.value++ }
}

// SharedFlow: バッファ付きブロードキャスト
val sharedFlow = MutableSharedFlow<String>(replay = 1)
callbackFlow応用

コールバックベースの API を Flow に変換するビルダー。trySend で値を送出し、awaitClose でクリーンアップを行う。

Kotlin
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*

fun locationFlow(): Flow<Location> = callbackFlow {
    val listener = LocationListener { location ->
        trySend(location)
    }
    locationManager.register(listener)
    awaitClose {
        locationManager.unregister(listener)  // コレクターが離れたら解除
    }
}
cold stream vs hot stream応用

cold stream(Flow)はコレクターごとに独立して実行される。hot stream(StateFlow/SharedFlow)は独立して動作し、複数コレクターで共有される。

Kotlin
// cold: collect するたびに flow ブロックが再実行
val cold: Flow<Int> = flow { emit(Random.nextInt()) }
cold.collect { println(it) }  // 毎回異なる値

// hot: StateFlow は共有された最新値を保持
val hot = MutableStateFlow(0)
// 複数コレクターが同じ値 (0) を受け取る
15

DSL・演算子オーバーロード

応用
演算子オーバーロード応用

operator 修飾子を付けた関数で演算子をオーバーロードできる。+plus-minus[]get/set などが対応している。

Kotlin
data class Vector(val x: Double, val y: Double) {
    operator fun plus(other: Vector)  = Vector(x + other.x, y + other.y)
    operator fun times(scale: Double) = Vector(x * scale, y * scale)
    operator fun unaryMinus()         = Vector(-x, -y)
}

val v1 = Vector(1.0, 2.0)
val v2 = Vector(3.0, 4.0)
println(v1 + v2)   // Vector(4.0, 6.0)
println(v1 * 2.0)  // Vector(2.0, 4.0)
型安全 DSL(@DslMarker)応用

@DslMarker アノテーションを使うとネストした DSL スコープで誤った外側スコープの呼び出しをコンパイルエラーにできる。

Kotlin
@DslMarker annotation class HtmlDsl

@HtmlDsl class Div {
    private val children = mutableListOf<String>()
    fun p(text: String) { children.add("<p>${text}</p>") }
    fun build() = "<div>${children.joinToString("")}</div>"
}

fun div(init: Div.() -> Unit): String = Div().apply(init).build()

val html = div {
    p("Hello")
    p("Kotlin DSL")
}
println(html)  // <div><p>Hello</p><p>Kotlin DSL</p></div>
buildList / buildMap応用

buildList { }buildMap { } はコレクションを DSL スタイルで構築するビルダー。内部は MutableList/MutableMap を使い、完了後にイミュータブルで返す。

Kotlin
val primes = buildList {
    add(2)
    addAll(listOf(3, 5, 7))
    if (true) add(11)
}  // List<Int>: [2, 3, 5, 7, 11]

val config = buildMap<String, Any> {
    put("host", "localhost")
    put("port", 8080)
    putAll(mapOf("debug" to true))
}
16

Kotlin Multiplatform・モジュール

応用
expect / actual(KMP)応用

expect で共通モジュールに API 宣言、actual でプラットフォーム固有の実装を提供する KMP の仕組み。

Kotlin
// commonMain
expect fun platformName(): String

// jvmMain
actual fun platformName(): String =
    "JVM ${System.getProperty("java.version")}"

// jsMain
actual fun platformName(): String =
    "Browser: ${js("navigator.userAgent")}"
@JvmStatic / @JvmOverloads / @JvmField応用

Java から Kotlin コードを自然に呼び出せるようにするアノテーション群。

Kotlin
class Config {
    companion object {
        @JvmStatic
        fun create() = Config()           // Java: Config.create()

        @JvmField
        val DEFAULT_TIMEOUT = 30_000      // Java: Config.DEFAULT_TIMEOUT(getter なし)
    }

    @JvmOverloads
    fun connect(host: String, port: Int = 8080, ssl: Boolean = false) { /* ... */ }
    // Java 側に connect(host), connect(host,port), connect(host,port,ssl) が生成される
}
@Serializable(kotlinx.serialization)応用

@Serializable アノテーションで JSON・CBOR などのシリアライズ/デシリアライズを自動生成。kotlinx.serialization ライブラリが必要。

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)          // {"name":"Alice","age":30}
val decoded = Json.decodeFromString<User>(json)
println(decoded.name)  // Alice
Kotlin Symbol Processing (KSP)応用

KSP はコンパイル時にアノテーションを処理しコードを生成するフレームワーク。kapt より高速で Kotlin を直接サポートしている。

Kotlin
// build.gradle.kts(KSP の設定例)
plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.21"
}

dependencies {
    ksp("com.example:my-processor:1.0.0")  // KSP プロセッサを追加
}

// 対象コード(プロセッサがアノテーションを処理してコードを生成)
@MyAnnotation
class Repository { /* ... */ }
🏠