本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

Kotlin チートシート — クイックリファレンス

Kotlin 2.0 の構文、null 安全、コルーチン、最もよく使う標準ライブラリをカバーするクイックリファレンス。日常の約 80% のシナリオを網羅します。

Kt

Kotlin Kotlin 2.0

JVM (kotlinc) / Multiplatform · オブジェクト指向・関数型・マルチプラットフォーム · 静的・強い型付け・型推論

おすすめの学習パス

最初に kotlinc のコンパイルと実行、fun main を学ぶ → 変数、型、制御フローを習得 → null 安全(null 許容型 ?、安全呼び出し ?.、エリビス ?:)と data class を理解 → ラムダ、拡張関数、コレクションの関数型操作を使う → クラス、インターフェース、ジェネリックを深く学ぶ → try / runCatching でエラー処理 → コルーチンと Flow で並行処理を書く → 必要に応じてネットワーク、時間、正規表現、Gradle ビルドを学ぶ。FAQ セクションは後で落とし穴を避けるのに適しています。

1.Hello World とビルド環境

Kotlin プログラムのコンパイルと実行:kotlinc ツールチェーン、スクリプト、パッケージ構成、コマンドライン引数。

最小プログラム

すべての Kotlin プログラムはトップレベルの fun main() をエントリーポイントとし、println で標準出力に書き出します。Kotlin 2.0 は引数なしの main をサポートします。

1
2
3
4
5
6
7
8
fun main() {
println("Hello, world!")
}
// Main.kt として保存し、kotlinc でコンパイル
// トップレベル関数はクラスで包む必要がない
// Kotlin 2.0 の main は引数なしで可
// 引数付きも可:fun main(args: Array<String>)
// 各行の末尾にセミコロンは不要

実行とビルド

kotlinc はソースを JVM バイトコードにコンパイルし、java -jar で実行します。Gradle は Kotlin プロジェクトの主流ビルドツールです。

1
2
3
4
5
6
7
8
9
10
11
// 実行可能な jar にコンパイル:
// kotlinc Main.kt -include-runtime -d hello.jar
// 実行:
// java -jar hello.jar
// 複数ファイルをコンパイル:
// kotlinc a.kt b.kt -include-runtime -d app.jar
// バイトコードのディレクトリのみコンパイル:
// kotlinc a.kt -d out
// ソースを直接実行(コンパイル不要):
// kotlin Main.kt
// バージョン確認:kotlinc -version

Kotlin スクリプト

.kts ファイルは直接実行でき、小さなツールや自動化に適しています。スクリプトは main 関数が不要で、トップレベルの文が順に実行されます。

1
2
3
4
5
6
7
8
9
10
11
// hello.kts
val name = "Kotlin"
println("Hello, $name!")
// 実行:kotlin hello.kts
// スクリプトはトップレベル式を許可:
val items = listOf(1, 2, 3)
println(items.sum())
// プロトタイプや日常の小タスクに適する
// プロジェクトコードはコンパイル型 main を使う
// スクリプト内でサードパーティ jar を直接呼べる:
// kotlin -classpath lib.jar app.kts

パッケージとインポート

package はファイルの所属パッケージを宣言し、import で別パッケージの型を取り込みます。Java と異なり、未使用の import はエラーになりません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.demo
import kotlin.math.sqrt
import kotlin.math.max as mx
import java.util.UUID
fun main() {
println(sqrt(16.0))
println(mx(1, 2))
println(UUID.randomUUID())
}
// ワイルドカードインポート:
// import kotlin.math.*
// トップレベル関数とクラスは既定で public
// 使わない import でもエラーにならない

コマンドライン引数

main の Array<String> 引数がコマンドライン引数を受け取り、args[0] が最初の引数です。引数は常に文字列なので自分で変換する必要があります。

1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
println("共 ${args.size} 个参数")
args.forEach { println(it) }
// 引数は常に文字列、自分で変換する:
val port = args.firstOrNull()
?.toIntOrNull() ?: 8080
println("端口: $port")
}
// 実行:kotlin Main.kt a b c
// args = [a, b, c]
// 堅牢な解析には toIntOrNull で例外を避ける

出力とフォーマット

println は出力して改行し、print は改行しません。文字列テンプレート ${} は式を埋め込み、最もよく使うフォーマット手段です。

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val name = "Kotlin"
val year = 2026
println("Hello, $name!") // 単純変数
println("今年是 ${year - 2025} 周年") // 式
print("不换行")
val pi = 3.14159
println("Pi = %.2f".format(pi)) // Pi = 3.14
println("%5d".format(42)) // " 42"
// printf スタイル(Java 相互運用):
System.out.printf("name=%s%n", name)
}

終了コード

exitProcess は即座にプログラムを終了し、終了コードを設定します。0 が成功、それ以外は失敗です。ライブラリコードでは呼び出さないでください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import kotlin.system.exitProcess
fun main(args: Array<String>) {
if (args.isEmpty()) {
println("用法: app <name>")
exitProcess(1)
}
println("Hello, ${args[0]}")
exitProcess(0)
}
// exitProcess は即座に終了、finally は実行されない
// 0 成功、非 0 失敗
// Java の System.exit と等価
// ライブラリコードでは exitProcess を呼ばない

環境構築

IntelliJ IDEA コミュニティ版は Kotlin を内蔵でサポートし、Android Studio も同様です。コマンドラインでは kotlinc と Gradle を使います。

1
2
3
4
5
6
7
8
9
10
// IntelliJ IDEA コミュニティ版(無料)
// 新規プロジェクトは Kotlin/JVM を選択
// Android Studio は Kotlin サポートを内蔵
// コマンドラインツール:kotlinc + Gradle
// インストール確認:
// kotlinc -version
// gradle -version
// 中国国内では Alibaba Cloud Maven ミラーの設定を推奨
// 初心者は Android 不要、JVM プロジェクトで直接実行
// マルチプラットフォームには追加で Kotlin Multiplatform プラグインが必要

2.変数と定数

変数宣言、val/var、定数、型推論、分解宣言、スコープ。

val と var

val は読み取り専用の参照を宣言し、var は可変の参照を宣言します。val を優先し、コンパイラは val にできる var を指摘します。

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val pi = 3.14159 // 読み取り専用、再代入不可
var counter = 0 // 可変
counter++
// val への再代入はコンパイルエラー:
// pi = 3.0
// val が指すオブジェクトの中身は変更できる:
val list = mutableListOf(1)
list.add(2) // 合法、参照は変わらない
}
// 原則:val にできるなら var を使わない
// val は参照の不変であって内容の不変ではない

型推論

コンパイラは初期化式から変数の型を推論し、明示的な型は省略可能です。推論結果は明示宣言と等価です。

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val count = 42 // Int
val name = "Kotlin" // String
val ratio = 3.14 // Double
val ok = true // Boolean
// 推論後は型が固定:
// val x = 1 は Int と推論
// 推論できないときだけ明示的な型:
val empty: List<Int> = emptyList()
val map: Map<String, Int> =
mapOf("a" to 1)
}

明示的な型

明示的な型は可読性を高め、null 安全性とジェネリクスでしばしば必要です。型は変数名の後にコロンで区切って書きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val name: String = "Rex"
var age: Int = 30
val height: Double = 1.75
val active: Boolean = true
// ヌル安全な型:
val maybe: String? = null
// 関数型の変数:
val handler: (Int) -> String =
{ it.toString() }
// 配列型:
val nums: IntArray = intArrayOf(1, 2, 3)
}
// 明示的な型は推論結果に影響しない
// チーム開発では境界を明示的に宣言するのが推奨

コンパイル時定数

const val はコンパイル時定数を宣言します。プリミティブ型と String のみ、トップレベルまたはコンパニオンオブジェクト内でしか宣言できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
const val MAX_SIZE = 100
const val APP_NAME = "GuruToolkit"
const val RATE = 0.15
fun main() {
println(MAX_SIZE)
}
// 基本型と String のみ
// トップレベルかコンパニオンオブジェクトでの宣言が必要
// 実行時の計算には val:
// val now = System.currentTimeMillis()
// const はコンパイル時にインライン展開される
// カスタム型には const は使えない

トップレベル変数

ファイルのトップレベルで変数や関数を直接宣言でき、クラスで包む必要はありません。トップレベルの val/var はファイルレベルのグローバル状態です。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ファイルレベル(クラスで包まない)
val appName = "demo"
var requestCount = 0
fun log(msg: String) {
println("[$appName] $msg")
}
// トップレベル宣言は既定で public
// 同じパッケージの他ファイルから直接参照できる
// 内部実装は private で露出を防ぐ:
private val secret = "hidden"
// トップレベルの初期化はクラス読み込み時に実行される
// val を多用し、可変なグローバル状態を避ける

分解宣言

分解宣言はオブジェクトを複数の変数に分割します。Pair、Triple、data class がすべてサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val (name, age) = "Alice" to 30
println(name) // Alice
println(age) // 30
val (x, y) = Pair(1, 2)
val (a, b, c) = Triple(1, 2, 3)
}
// data class は分解を自動サポート:
data class Point(val px: Int, val py: Int)
fun show(p: Point) {
val (px, py) = p
println("$px,$py")
}
// 不要な部分はアンダースコアで:
// val (first, _) = listOf(1, 2)

型エイリアス

typealias は既存型に別名を付けて可読性を高めます。新しい型を作らず、元の型と完全に等価です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typealias UserId = Long
typealias Handler = (String) -> Unit
typealias NameMap = Map<String, String>
fun main() {
val id: UserId = 1001
val handle: Handler = { println(it) }
val names: NameMap = mapOf("a" to "b")
handle(names["a"] ?: id.toString())
}
// ジェネリクスのエイリアス:
// typealias IntList = List<Int>
// 単なる別名で、型チェックには影響しない
// 関数型のエイリアスでシグネチャが読みやすくなる

スコープとブロック

変数のスコープは波括弧ブロックで決まり、内側からは外側の変数にアクセスできます。同じスコープで同名変数を再宣言できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val outer = 10
if (outer > 0) {
val inner = outer * 2 // ブロック内ローカル
println(inner)
}
// inner はここでは見えない
// 同じスコープで再宣言はできない:
// val x = 1; val x = 2 エラー
// 内側は外側の変数を遮蔽できる:
val name = "外层"
println(name)
}
// 遮蔽は混乱を招くのでなるべく避ける

3.データ型

数値、文字、真偽値、配列、範囲、data class、型変換。

数値型

Kotlin は Byte/Short/Int/Long/Float/Double の 6 種類の数値型を提供し、接尾辞 L、f、u で具体的な型を指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val b: Byte = 127
val s: Short = 32767
val i: Int = 2_147_483_647
val l: Long = 9_000_000_000L // 接尾辞 L
val f: Float = 3.14f // 接尾辞 f
val d: Double = 3.14159
// アンダースコア区切りで可読性が上がる:
val million = 1_000_000
// 16 進数と 2 進数:
val hex = 0xFF
val bin = 0b1010
}
// 符号なし型(1.5+):val u: UInt = 42u

数値演算

算術、ビット演算、数学関数。Int 同士の除算は Int で、整数除算は切り捨てられることに注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlin.math.abs
import kotlin.math.max
fun main() {
val a = 7
val b = 2
println(a / b) // 3(整数除算は切り捨て)
println(a % b) // 1(剰余)
println(a.toDouble() / b) // 3.5
println(abs(-5))
println(max(3, 9))
// ビット演算:
println(1 shl 4) // 16
println(0b1100 and 0b1010)
}

文字と真偽値

Char は単一の文字(単一引用符)を表し、Boolean は true/false のみです。文字には完全なチェックと変換メソッドがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val letter: Char = 'A'
val digit: Char = '9'
val flag: Boolean = true
// 文字のメソッド:
println(letter.isLetter()) // true
println(digit.isDigit()) // true
println(letter.lowercaseChar())
println(letter.code) // 65 ASCII コード
// 真偽値の演算:
println(flag && true)
println(flag || false)
println(!flag)
}
// エスケープ文字:val tab = '\t'
// 真偽値に 0/1 の意味はなく、算術には使えない

配列

Array<T> は参照型の配列、IntArray などはプリミティブ型の配列(より効率的)です。配列サイズは固定です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val nums = arrayOf(1, 2, 3) // Array<Int>
val ints = intArrayOf(1, 2, 3) // IntArray
val strs = arrayOf("a", "b")
println(nums[0])
nums[1] = 99
println(nums.size)
// 指定サイズの配列:
val zeros = IntArray(5) // 全部 0
// 初期化ラムダ:
val squares = IntArray(5) { it * it }
for (n in ints) println(n)
}
// 配列の共変の落とし穴は FAQ 節を参照

範囲

範囲 a..b は閉区間、until は右側を開いた範囲です。for ループや in 判定と組み合わせてよく使います。

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val r1 = 1..10 // 10 を含む
val r2 = 1 until 10 // 10 を含まない
val r3 = 10 downTo 1 // 降順
val r4 = 1..10 step 2 // ステップ 2
println(5 in r1) // true
println(11 in r1) // false
for (i in 1..3) println(i)
// 文字の範囲:
for (c in 'a'..'c') print(c)
}
// 範囲は主に Int/Long/Char で使用

文字列型

String は不変の UTF-16 文字シーケンスです。文字列リテラルは二重引用符、エスケープはバックスラッシュを使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val s: String = "Hello"
val quote = "say \"hi\""
// 不変:s[0] = 'h' はエラーになる
val greeting = "Hi, " + "Kotlin"
println(greeting)
// 走査とインデックス:
for (c in "abc") println(c)
println("Kotlin"[1]) // 'o'
println("Kotlin".length) // 6
// 部分文字列:
println("Kotlin".substring(1, 4)) // otl
}
// 文字列の比較は ==(構造的等価)

data class

data class は equals/hashCode/toString/copy と分解を自動生成し、データを保持する純粋な値オブジェクトに使われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
data class Point(val x: Int, val y: Int)
fun main() {
val p = Point(1, 2)
val q = p.copy(y = 99) // コピーして変更
println(p) // Point(x=1, y=2)
println(p == Point(1, 2)) // true 構造的等価
val (x, y) = p // 分解
println("$x, $y")
}
// data class の主コンストラクタは最低 1 つの val/var が必要
// copy は浅いコピー、ネストしたオブジェクトは共有される
// DTO や値オブジェクトに適する

型変換

Kotlin は暗黙的に数値型を変換しないため、明示的な toXxx() メソッドが必要です。小さい型から大きい型でもオーバーフローしうるので注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val i: Int = 42
val l: Long = i.toLong() // 明示的変換
val d: Double = i.toDouble()
// String から数値へ:
val n = "42".toInt()
val safe = "abc".toIntOrNull() // null
val s = 42.toString()
// オーバーフローの切り捨て:
val big = 1000.toByte() // -24
}
// 暗黙変換はされない:
// val x: Long = 42 コンパイルエラー
// toXxxOrNull 系で例外を避ける

Unit と Nothing

Unit は戻り値がないことを表し(Java の void に類似)、Nothing は決して戻らないことを表し、必ず投げる関数や未実装関数に使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun log(msg: String): Unit {
println(msg)
}
// Unit は宣言を省略できる:
fun log2(msg: String) {
println(msg)
}
// Nothing:決して戻らない
fun fail(msg: String): Nothing {
throw IllegalStateException(msg)
}
// 未実装のプレースホルダ:
fun todo(): Nothing =
throw NotImplementedError("未实现")
// Nothing はすべての型のサブタイプ
// 任意の期待される型の位置に使える

4.参照と null 安全

Kotlin には生ポインタはありません:参照意味論、null 許容型、安全呼び出し、エリビス演算子、lateinit、値クラス。

参照とオブジェクト

Kotlin には生ポインタがなく、すべての変数は参照です。JVM が自動的にライフサイクルを管理し、ポインタ演算はありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val a = "hello"
val b = a // 同じオブジェクトを参照、コピーではない
println(a === b) // true 同一参照
// & でアドレス取得も * でデリファレンスもない
// 基本型もオブジェクト(可空にするとボックス化される)
val n1 = 100
val n2 = 100
println(n1 == n2) // true 構造的等価
}
// === は参照が同一オブジェクトかを比較
// == は内容が等しいかを比較
// 手動でのメモリ解放は不要、GC が自動回収する

null 許容型

型に ? を付けると null を許容します。null 許容型は直接メソッドを呼び出せず、必ず null チェックが必要で、これが Kotlin の null 安全に重要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
var name: String = "Kotlin" // 非null
var maybe: String? = null // 可空
// 直接呼べない:maybe.length はコンパイルエラー
// 判空してから使う:
if (maybe != null) {
println(maybe.length)
}
// 判空後はスマートキャストで安全にアクセスできる
}
// Java 由来の可空型はアノテーションと組み合わせる:
// @Nullable -> 可空、@NotNull -> 非null
// Java でアノテーションのない型はプラットフォーム型(String!)

安全呼び出し

?. はオブジェクトが非 null のときメソッドを呼び出し、null のとき式全体が null になります。安全呼び出しはチェーンできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
println(maybe?.length) // null
val sure: String? = "hi"
println(sure?.length) // 2
}
// チェーンで安全呼び出し:
data class User(val name: String?)
fun show(u: User?) {
val len = u?.name?.length
println(len)
}
// let と組み合わせた判空処理:
// maybe?.let { println("非空: $it") }
// ?. は可空型を返す、続けて処理するかデフォルト値を与える

Elvis 演算子

?: は左辺が null のとき右辺の既定値を返します。右辺は式、早期 return、throw も可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
val result = maybe ?: "default"
println(result) // default
// ?. と組み合わせた処理チェーン:
val len = maybe?.length ?: 0
val upper = maybe?.uppercase() ?: ""
}
// 右辺で早期 return も可能:
fun read(input: String?): String {
return input ?: return "空"
}
// または例外を投げる:
// val value = maybe ?: throw IllegalArgumentException()
// Elvis の左辺は可空、右辺は非null であること

非 null 表明

!! は null 許容型を強制的に非 null に変換し、null のとき NPE を投げます。使用は避け、論理的に確実に非 null の場合のみ使ってください。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val maybe: String? = "hello"
val len = maybe!!.length // 非null を強制
println(len)
// null なら NullPointerException を投げる
}
// より良い代替:
// 1. 安全呼び出し + Elvis:
// val l = maybe?.length ?: 0
// 2. 事前検証で明確なエラー:
// val v = maybe ?: error("配置缺失")
// 3. require で事前条件を検証
// チームレビューで !! の濫用を止めるべき

lateinit プロパティ

lateinit は後で初期化する非 null の var を宣言し、依存注入やフレームワークコールバックに使います。初期化前にアクセスすると例外が投げられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lateinit var config: String
fun setup() {
config = "initialized"
}
fun use() {
// アクセス前に初期化済みかを確認:
if (::config.isInitialized) {
println(config)
}
}
// 制限:var のみ、非null 型
// 基本型(Int など)には使えない
// ローカル変数には使えない
// 代替:可空型 + 判空、または by lazy

by lazy 委譲

by lazy は初回アクセス時に初期化を実行し、結果をキャッシュします。スレッド安全で、高価な一度きりの初期化に適しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val config: String by lazy {
println("首次访问才执行")
"计算出的配置"
}
println(config) // 初期化ログが出力される
println(config) // キャッシュを使用、再実行しない
}
// スレッドモードの指定:
// val v by lazy(LazyThreadSafetyMode.PUBLICATION)
// 既定は SYNCHRONIZED、マルチスレッド安全
// 読み取り専用、val に適する
// 依存注入では lateinit か by lazy がよく使われる

コピーと等価性

== は構造的等価を比較し、=== は参照の同一性を比較します。data class の copy() はシャローコピーです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
data class Person(val name: String, val age: Int)
fun main() {
val a = Person("Alice", 30)
val b = Person("Alice", 30)
println(a == b) // true 内容が等しい
println(a === b) // false 別オブジェクト
val c = a.copy(age = 31)
println(c) // Person(name=Alice, age=31)
}
// ネストしたオブジェクトは浅いコピーで共有:
data class Wrapper(val list: MutableList<Int>)
fun test() {
val w1 = Wrapper(mutableListOf(1))
val w2 = w1.copy()
w2.list.add(2)
println(w1.list) // [1, 2] 共有!
}
// 深いコピーは手動実装かシリアライズが必要

value class

value class は単一の値をラップし実行時にインライン化され、ボクシングのオーバーヘッドを排除します。タイプセーフなカスタムラッパーのようなものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@JvmInline
value class UserId(val id: Long)
fun process(userId: UserId) {
println(userId.id)
}
fun main() {
val id = UserId(1001)
process(id)
}
// @JvmInline アノテーションが必要(Kotlin 1.5+)
// プロパティは 1 つだけ
// コンパイル時にインライン化され、実行時はラッパーオブジェクトなし
// ラッパー型に適する:UserId、Money など
// open クラスや配列要素などの場面には使えない

5.制御フロー

if、when、for、while、スマートキャスト — 式スタイルの制御フロー。

if 式

if は式であり、値を返して変数に代入できます。Kotlin には三項演算子がなく、if-else がその代わりです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val score = 85
val grade = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else {
"C"
}
println(grade) // B
// 単一行の書き方:
val sign = if (score > 0) "正" else "负"
println(sign)
}
// 分岐の戻り型は一致するか共通のスーパータイプが必要

when 式

when は switch を置き換え、値、範囲、型、条件分岐をサポートします。式形式では else が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {
val status = 404
val msg = when (status) {
200 -> "OK"
404 -> "未找到"
in 500..599 -> "服务器错误"
else -> "其他"
}
println(msg)
// 引数なし when(if-else の連鎖に相当):
val n = 10
when {
n > 0 -> println("正数")
n < 0 -> println("负数")
else -> println("零")
}
// 複数分岐のまとめ:
when (status) {
200, 201 -> println("成功")
else -> println("其他")
}
}

for ループ

for は範囲、コレクション、配列などの反復可能オブジェクトを走査します。Kotlin には C スタイルの三項 for はありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// 範囲を走査:
for (i in 1..5) println(i)
// コレクションを走査:
val items = listOf("a", "b", "c")
for (item in items) println(item)
// インデックス付き:
for ((index, value) in items.withIndex()) {
println("$index -> $value")
}
// map を走査:
val map = mapOf("a" to 1, "b" to 2)
for ((k, v) in map) println("$k=$v")
}
// 降順:for (i in 5 downTo 1)
// ステップ:for (i in 0 until 10 step 2)

while ループ

while は判定してから実行、do-while は実行してから判定します。条件は Boolean 式でなければなりません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
var n = 0
while (n < 3) {
println(n)
n++
}
// do-while は最低 1 回実行される:
var m = 5
do {
println(m)
m++
} while (m < 3)
// 無限ループ:
while (true) {
break
}
}
// ループ変数を更新して無限ループを避ける
// コレクション走査は for / forEach を優先

break と continue

break はループを抜け、continue は現在の繰り返しをスキップします。ラベル @ はネストしたループのジャンプを制御します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
for (i in 1..5) {
if (i == 3) continue // 3 をスキップ
if (i == 5) break // ループを抜ける
println(i) // 1 2 4
}
// ラベルで外側のループを抜ける:
outer@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) continue@outer
println("$i,$j")
}
}
// 出力:1,1 2,1 3,1
}
// ラベルは return にも使える:return@label

スマートキャスト

型チェックの後、コンパイラが自動的に型を変換し、明示的なキャストは不要です。null チェックや is チェックの後、安全に使用できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(shape: Shape): Double = when (shape) {
is Circle -> Math.PI * shape.r * shape.r
is Rect -> shape.w * shape.h
}
fun main() {
val obj: Any = "hello"
if (obj is String) {
println(obj.length) // 自動で String に変換
}
println(area(Circle(1.0)))
}
// 前提:チェック後に変数が変更されていないこと
// 明示的キャスト as は必要なときだけ使う

ラベルとジャンプ

ラベル @ はループやラムダに印を付け、break、continue、return と組み合わせて正確なジャンプを実現します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
// ループのラベル:
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i * j > 4) break@outer
println("$i x $j")
}
}
// lambda のラベル付き return:
val list = listOf(1, 2, 3, 4)
list.forEach {
if (it == 3) return@forEach // この回をスキップ
println(it) // 1 2
}
}
// ラベルなしの return は外側の関数を抜ける
// ラベルは任意に命名できる:myLoop@

takeIf と takeUnless

takeIf は条件を満たすなら元のオブジェクトを返し、そうでなければ null です。takeUnless は逆で、チェーンでの null 判定やフィルタリングによく使われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val n = 10
val positive = n.takeIf { it > 0 }
println(positive) // 10
val neg = n.takeUnless { it > 0 }
println(neg) // null
// ?. と組み合わせる:
val text: String? = "hello"
val upper = text?.takeIf { it.isNotBlank() }
?.uppercase()
println(upper)
// フィルタ後に判空:
val list = listOf(1, 2, 3)
val first = list.firstOrNull()
?.takeIf { it > 5 }
println(first) // null
}

6.関数とラムダ

関数宣言、ラムダ、高階関数、拡張関数、スコープ関数、中置関数。

関数宣言

fun で関数を宣言し、引数は型を持ち、戻り型は引数リストの後に書きます。戻り値がないときは Unit を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun add(a: Int, b: Int): Int {
return a + b
}
fun greet(name: String) {
println("Hello, $name")
}
// 戻り型なしは既定で Unit:
fun log(msg: String) { println(msg) }
fun main() {
val sum = add(1, 2)
greet("Kotlin")
log("sum = $sum")
}
// 引数は値渡し(参照型は参照を渡す)
// 関数は既定で public

単一式関数

関数本体が単一の式の場合、= で簡潔に書け、return と波括弧を省略できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun square(x: Int) = x * x
fun isEven(n: Int) = n % 2 == 0
fun desc(n: Int) = if (n > 0) "正" else "非正"
// 等価な書き方:
fun square2(x: Int): Int {
return x * x
}
fun main() {
println(square(5)) // 25
println(isEven(4)) // true
println(desc(-1)) // 非正
}
// 単純な純関数に適する
// 複雑なロジックはブロック本体の方が読みやすい

デフォルト引数と名前付き引数

引数には既定値を設定でき、呼び出し時に省略可能です。名前付き引数は任意の順で渡せ、可読性が向上します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun greet(name: String, prefix: String = "Hi") {
println("$prefix, $name!")
}
fun main() {
greet("Alice") // Hi, Alice!
greet("Bob", "Hello") // Hello, Bob!
// 名前付き引数(デフォルトをスキップ):
greet(name = "Cathy", prefix = "你好")
// 順序も調整できる:
greet(prefix = "Hola", name = "Dan")
}
// デフォルト値の後の引数:
// fun format(v: Int, base: Int = 10, pad: Int = 0)
// 真偽値の引数は名前付き引数で明確にする

可変長引数

vararg は任意の数の引数を受け取り、関数内では配列として使えます。展開演算子 * で配列を渡せます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun sum(vararg nums: Int): Int {
return nums.sum()
}
fun main() {
println(sum(1, 2, 3)) // 6
println(sum(1, 2, 3, 4, 5)) // 15
// 配列を展開して渡す:
val arr = intArrayOf(10, 20)
println(sum(*arr)) // 30
}
// vararg パラメータの型は配列:
// vararg nums: Int -> IntArray
// 通常の引数と併用できる:
// fun build(prefix: String, vararg parts: String)
// 通常は引数リストの末尾に置く

ラムダ式

ラムダは無名関数で、波括弧で包み、-> で引数と本体を区切ります。最後の引数がラムダのときはトレイリングラムダ構文が使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val sum = { a: Int, b: Int -> a + b }
println(sum(1, 2)) // 3
// 引数 1 つは宣言を省略して it を使う:
val list = listOf(1, 2, 3)
println(list.map { it * 2 }) // [2, 4, 6]
// 末尾 Lambda 構文:
list.filter { it > 1 }
.forEach { println(it) }
// 明示的な型:
val greet: (String) -> String =
{ "Hello, $it" }
println(greet("Kotlin"))
}
// Lambda は外側の変数をキャプチャできる

高階関数

高階関数は関数を引数に取ったり戻り値にしたりします。map/filter など標準ライブラリの高階関数は日々の主力です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun applyTwice(f: (Int) -> Int, x: Int): Int {
return f(f(x))
}
fun main() {
val inc = { n: Int -> n + 1 }
println(applyTwice(inc, 5)) // 7
// よく使う高階関数:
val nums = listOf(1, 2, 3, 4)
println(nums.map { it * 2 }) // 変換
println(nums.filter { it % 2 == 0 }) // フィルタ
println(nums.fold(0) { acc, n -> acc + n }) // 累積
println(nums.any { it > 3 }) // どれか満たす
println(nums.all { it > 0 }) // すべて満たす
}
// 関数を返す:
fun makeAdder(step: Int): (Int) -> Int = { it + step }

拡張関数

拡張関数は既存クラスにメソッドを追加し、元のクラスを変更しません。this はレシーバを指し、スコープは宣言箇所に限定されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
fun main() {
println("level".isPalindrome()) // true
println("hello".isPalindrome()) // false
// ジェネリック拡張:
println(listOf(1, 2).secondOrNull())
}
// ジェネリック拡張の定義:
fun <T> List<T>.secondOrNull(): T? =
if (size >= 2) this[1] else null
// 呼び出しは通常のメソッドと同様
// 拡張はレシーバ型を変えない

中置関数

infix キーワードで関数をスペース区切りで演算子のように呼び出せます。メンバ関数または拡張関数で、引数は 1 つでなければなりません。

1
2
3
4
5
6
7
8
9
10
11
12
13
infix fun Int.times(str: String): String {
return str.repeat(this)
}
fun main() {
println(3 times "hi") // hihihi
// 標準ライブラリの中置関数:
val map = mapOf("a" to 1) // to は中置
println(2 in 1..3) // in は中置
}
// カスタム中置関数:
// infix fun String.concat(b: String) = this + b
// println("foo" concat "bar") // foobar
// 要件:メンバまたは拡張、単一引数、デフォルト値なし

スコープ関数

let/run/with/apply/also はオブジェクトのコンテキストでコードを実行します。let は結果を返し、apply はレシーバを返します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
data class User(var name: String, var age: Int)
fun main() {
val user = User("Alice", 30)
// let:結果を返し、it がオブジェクトを参照
val greeting = user.let { "Hi ${it.name}" }
println(greeting)
// apply:レシーバを返し、初期化によく使う
val u2 = User("Bob", 0).apply {
age = 25
}
println(u2)
// run:結果を返し、this がオブジェクトを参照
val info = user.run { "$name$age 岁" }
println(info)
// also:レシーバを返し、副作用に使う
user.also { println(it.name) }
// with:非拡張、this がオブジェクトを参照
val desc = with(user) { "$name/$age" }
println(desc)
}

ローカル関数

関数内ではローカル関数を定義でき、外側の変数をキャプチャします。ロジックの整理や重複削減に使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun process(nums: List<Int>) {
fun isPositive(n: Int) = n > 0
fun show(label: String, v: Int) {
println("$label: $v")
}
val positives = nums.filter(::isPositive)
show("正数个数", positives.size)
}
// 局部関数は外側の変数にアクセスできる:
fun stats(nums: List<Int>) {
val total = nums.sum()
fun percent(n: Int) = n * 100.0 / total
println(percent(1))
}
fun main() {
process(listOf(-1, 2, -3, 4))
stats(listOf(1, 2))
}
// 外側の関数内でのみ見える
// 再帰のヘルパー関数のカプセル化に適する

7.文字列

文字列の基礎、テンプレート、生文字列、メソッド、連結、フォーマット、変換。

文字列の基礎

String は不変の文字シーケンスです。二重引用符リテラル、エスケープシーケンス、インデックスアクセス、繰り返しアクセス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val s = "Hello, Kotlin"
println(s.length) // 13
println(s[0]) // 'H'
println(s.first()) // 'H'
println(s.last()) // 'n'
println(s.isEmpty()) // false
println(s.isBlank()) // false
println(s.lowercase())
println(s.uppercase())
// 接頭辞・接尾辞:
println(s.startsWith("Hello")) // true
println(s.endsWith("lin")) // true
println(s.contains("Kot")) // true
}
// 不変:変更するたびに新しい文字列を返す

文字列テンプレート

$変数 と ${式} で値を文字列に埋め込み、最もよく使う文字列構築手法です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val name = "Kotlin"
val year = 2026
println("Hello, $name!") // Hello, Kotlin!
println("今年是 ${year - 2025} 周年")
// オブジェクトのプロパティ:
val u = User("Alice")
println("用户: ${u.name}")
// メソッド呼び出し:
val list = listOf(1, 2)
println("数量: ${list.size}")
// $ のエスケープ:
println("\$100")
}
data class User(val name: String)
// 複雑な式は波括弧が必要

生文字列

三重引用符 """ の生文字列は改行と書式を保持し、エスケープしません。trimIndent で共通のインデントを除去できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
// 生文字列:三重引用符で囲み、エスケープを処理しない
val path = """C:\Users\name\docs"""
println(path) // バックスラッシュはそのまま
// 複数行の生文字列は改行と空白を保持:
// val sql = """
// SELECT *
// FROM users
// """.trimIndent()
// trimIndent() は各行の共通インデントを除去
// テンプレートも使える:
val name = "Kotlin"
val msg = """Hello $name"""
println(msg)
}
// バックスラッシュと引用符のエスケープ不要
// 複数行テキスト、SQL、JSON に適する
// 単一行の生文字列も合法:"""abc"""

よく使うメソッド

標準ライブラリは豊富な文字列メソッドを提供します:切り出し、置換、空判定、トリム、パディング。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val s = " Hello World "
println(s.trim()) // 空白除去
println(s.substring(2, 7)) // "Hello"
println(s.replace("World", "Kt"))
println(s.reversed())
println(s.padStart(20, '*'))
println("".isEmpty()) // true
println(" ".isBlank()) // true
// インデックス検索:
val t = "banana"
println(t.indexOf('a')) // 1
println(t.lastIndexOf('a')) // 5
println(t.indexOf("na")) // 2
println("ab".repeat(3))
}

連結と StringBuilder

ループでの連結は StringBuilder(または buildString)を使い、大量の中間文字列生成を避けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
// 少量の連結は + やテンプレートで十分:
val a = "foo"
val b = "bar"
println("$a-$b")
// ループ内の連結は StringBuilder:
val sb = StringBuilder()
for (i in 1..5) {
sb.append("item$i; ")
}
println(sb)
// 関数型 buildString:
val result = buildString {
append("start")
for (i in 1..3) append(i)
}
println(result) // start123
// joinToString でコレクションを連結:
println(listOf("a", "b").joinToString(", "))
}
// + はループ内で毎回新しい文字列を作る

フォーマット

format メソッドは printf 風のフォーマット出力を実装し、幅、精度、型プレースホルダをサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val name = "Kotlin"
val score = 92.5
println("姓名: %s".format(name))
println("分数: %.1f".format(score)) // 92.5
println("补零: %05d".format(42)) // 00042
println("左对齐: %-8s|".format(name))
println("%x".format(255)) // ff
println("%b".format(true)) // true
// 幅の指定:
println("%10s".format(name))
}
// Java の String.format と等価:
// String.format("Hello %s", name)
// 書式文字列の誤りはコンパイルエラーにならず実行時に例外

文字列変換

文字列と数値の相互変換、Boolean への変換。toXxxOrNull 系は安全な変換で例外を回避します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val s = "42"
println(s.toInt()) // 42
println(s.toLong())
println("3.14".toDouble())
// 安全な変換、失敗時は null:
println("abc".toIntOrNull()) // null
println("42".toIntOrNull() ?: 0)
// 数値を文字列に:
println(42.toString())
println(3.14.toString())
// 真偽値への変換:
println("true".toBooleanStrictOrNull())
// 文字配列に:
val chars = "hi".toCharArray()
println(chars.size)
}

分割と結合

split は区切り文字でリストに分割し、joinToString はコレクションを文字列に結合します。複数の区切り文字もサポート。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val csv = "a,b,c"
val parts = csv.split(",")
println(parts) // [a, b, c]
// 複数セパレータ:
println("a-b_c".split('-', '_'))
// コレクションを文字列に:
val nums = listOf(1, 2, 3)
println(nums.joinToString(", ")) // 1, 2, 3
println(nums.joinToString(", ", "[", "]"))
// 分割数の制限:
println("a,b,c".split(",", limit = 2))
// 正規表現セパレータ:
println("a, b; c".split(Regex("[,;\\s]+")))
}

8.コレクション

List、Set、Map、遅延シーケンス、変換操作、グループ化、ソート。

List

listOf は読み取り専用リストを作成し、elementAt と get でインデックスアクセスします。既定の List インターフェースは変更できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val nums = listOf(1, 2, 3, 2)
println(nums[0]) // 1
println(nums.elementAt(1)) // 2
println(nums.size) // 4
println(nums.first()) // 1
println(nums.last()) // 2
println(nums.indexOf(2)) // 1
println(nums.contains(3)) // true
// 空リスト:
val empty = emptyList<Int>()
println(empty)
// 指定要素で構築:
println(List(3) { it * 10 }) // [0, 10, 20]
}
// 範囲外アクセスは IndexOutOfBounds を投げる
// firstOrNull で例外を避ける

Set

setOf は重複なしのコレクションを作成し、要素は一意です。存在判定は List より高速です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val tags = setOf("a", "b", "a")
println(tags) // [a, b] 自動で重複排除
println("a" in tags) // true
println(tags.contains("c")) // false
// 集合演算:
val s1 = setOf(1, 2, 3)
val s2 = setOf(2, 3, 4)
println(s1 intersect s2) // [2, 3]
println(s1 union s2) // [1, 2, 3, 4]
println(s1 subtract s2) // [1]
println(s1.isEmpty())
}
// 挿入順を保持(LinkedHashSet)
// 重複排除の集計には toSet

Map

mapOf はキーと値のペアを作成し、to または Pair でエントリを構築します。キーで値を取り、未知のキーは既定値を返せます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"]) // 30
println(ages["Cathy"]) // null
// 安全な取得:
println(ages.getValue("Alice"))
println(ages.getOrDefault("Cathy", 0))
println(ages.getOrElse("Cathy") { -1 })
// キーの存在判定:
println("Alice" in ages)
println(ages.containsKey("Bob"))
// 走査:
for ((k, v) in ages) println("$k=$v")
println(ages.keys)
println(ages.values)
}

可変コレクション

mutableListOf/mutableSetOf/mutableMapOf は可変コレクションを作成します。読み取り専用ビューは add できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
val list = mutableListOf(1, 2)
list.add(3) // 末尾
list.add(0, 0) // 指定位置
list.removeAt(1)
list[0] = 99
println(list) // [99, 2, 3]
// 可変 Set:
val set = mutableSetOf("a")
set.add("b")
set.remove("a")
// 可変 Map:
val map = mutableMapOf("a" to 1)
map["b"] = 2
map.remove("a")
// 一括操作:
list.addAll(listOf(4, 5))
list.removeAll(listOf(2, 3))
println(list)
}
// 読み取り専用ビューも同じ下層データを指す

遅延シーケンス

Sequence は遅延評価で、チェーン操作は終端操作まで要素ごとに実行されます。大量データでは中間コレクションを避けられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val nums = (1..100).asSequence()
.filter { it % 2 == 0 }
.map { it * it }
.take(5)
.toList()
println(nums) // [4, 16, 36, 64, 100]
// 無限シーケンス:
val naturals = generateSequence(1) { it + 1 }
naturals.take(3).forEach { println(it) }
}
// List との違い:
// List チェーンは毎ステップで新コレクション生成
// Sequence は単一パスで処理し、メモリ節約
// データ量が大きいときは Sequence を使う
// 終端操作:toList、sum、count

変換操作

map/filter/flatMap などはコレクション処理の主力で、手書きループを関数型スタイルに置き換えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val nums = listOf(1, 2, 3, 4)
println(nums.map { it * 2 })
println(nums.filter { it % 2 == 0 })
println(nums.flatMap { listOf(it, it) })
// 集計:
println(nums.sum())
println(nums.average())
println(nums.max())
println(nums.fold(0) { acc, n -> acc + n })
println(nums.reduce { acc, n -> acc + n })
// グループ化:
println(nums.groupBy { it % 2 == 0 })
// 重複排除とソート:
println(nums.distinct())
println(nums.sorted())
println(nums.first { it > 2 })
}

グループ化と関連付け

groupBy は条件でグループ化し、associate はキーと値の関係を構築し、partition は二つに分割します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
data class User(val name: String, val dept: String)
val users = listOf(
User("A", "dev"), User("B", "dev"),
User("C", "ops"),
)
// グループ化:
val byDept = users.groupBy { it.dept }
println(byDept.keys) // [dev, ops]
// associate で map を構築:
val nameToDept = users.associate {
it.name to it.dept
}
println(nameToDept)
// 分割:
val nums = listOf(1, 2, 3, 4, 5)
val (even, odd) = nums.partition { it % 2 == 0 }
println(even) // [2, 4]
println(odd) // [1, 3, 5]
println(nums.count { it > 3 })
}

ソート

sorted/sortedBy は新しいソート済みリストを返し、sort は可変リストをインプレースでソートします。カスタム比較子をサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val nums = listOf(3, 1, 2)
println(nums.sorted()) // [1, 2, 3]
println(nums.sortedDescending()) // [3, 2, 1]
data class User(val name: String, val age: Int)
val users = listOf(User("A", 30), User("B", 20))
println(users.sortedBy { it.age })
println(users.sortedByDescending { it.name })
// 複数フィールド:
println(users.sortedWith(
compareBy<User> { it.age }.thenBy { it.name }
))
// 破壊的ソート(可変コレクション):
val m = mutableListOf(3, 1, 2)
m.sort()
println(m)
}
// reversed() は反転

コレクション間の変換

コレクション、配列、Map の相互変換。toList/toSet/toMap/toTypedArray。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val list = listOf(1, 2, 2, 3)
println(list.toSet()) // [1, 2, 3]
println(list.toMutableList())
// 配列との相互変換:
val arr = intArrayOf(1, 2, 3)
println(arr.toList())
println(listOf(1, 2).toIntArray().contentToString())
// List を Map に:
val pairs = listOf("a" to 1, "b" to 2)
println(pairs.toMap())
// 文字列をコレクションに:
println("a,b,c".split(","))
}
// toSet は自動で重複排除
// 空コレクションの型推論:listOf<Int>()

9.メモリとパフォーマンス

JVM ガベージコレクション、オブジェクト参照、ボクシング、割り当て最適化、パフォーマンス分析。

JVM ガベージコレクション

Kotlin/JVM は GC でメモリを自動管理し、手動解放は不要です。到達不能になったオブジェクトは回収されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Kotlin は JVM 上で動作し、メモリは自動管理
// 手動解放は不要、到達不能になったオブジェクトは回収される
fun create() {
val temp = StringBuilder() // ローカルオブジェクト
// 関数の戻り後にガベージになる
}
fun main() {
// 大量の一時オブジェクトで GC が発生:
for (i in 1..100000) {
val s = "item$i"
}
// 割り当てを減らすと GC の負荷が下がる:
val sb = StringBuilder()
for (i in 1..100000) {
sb.clear()
sb.append("item$i")
}
println(sb.length)
}
// 弱参照:WeakReference キャッシュは回収を妨げない

オブジェクト参照

Kotlin の全オブジェクトは参照経由でアクセスし、ポインタはありません。参照型が共有と到達可能性を決定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.ref.WeakReference
fun main() {
val list1 = mutableListOf(1, 2)
val list2 = list1 // 同じオブジェクト
list2.add(3)
println(list1) // [1, 2, 3] 共有!
}
// 弱参照:GC の回収を妨げない
fun demo() {
val weak = WeakReference(mutableListOf(1))
println(weak.get()) // null の可能性がある
}
// 軟参照 SoftReference はメモリ不足のときだけ回収
// 循環参照は GC が処理できる(到達性解析)

inline 関数

inline は呼び出し点で関数本体を展開し、ラムダオブジェクト生成のオーバーヘッドを排除します。reified ジェネリックには inline が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun measure(block: () -> Unit) {
val start = System.nanoTime()
block()
println("耗时: ${System.nanoTime() - start}")
}
// inline はインライン展開し、Lambda オブジェクトのコストなし:
inline fun measureInline(block: () -> Unit) {
val start = System.nanoTime()
block()
}
fun main() {
measureInline { Thread.sleep(1) }
}
// reified ジェネリクスは inline と組み合わせる:
inline fun <reified T> cast(obj: Any): T? =
obj as? T
// 過度のインライン化はバイトコードのサイズを増やす

割り当て最適化

オブジェクト割り当ての削減は JVM パフォーマンスの鍵です。オブジェクト再利用、ボクシング回避、プリミティブ型配列の使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// ボックス化を避ける:
val ints = IntArray(100) // 基本型
val boxed = Array(100) { 0 } // ボックス化 Int
// 中間コレクションを避ける:
val result = (1..1000)
.asSequence()
.filter { it % 2 == 0 }
.toList()
println(ints.size + boxed.size + result.size)
// 可変コレクションの再利用:
val buffer = mutableListOf<Int>()
println(buffer)
}
// 文字列連結は buildString
// BAD:ループ内の + 連結で文字列を毎回生成

プリミティブ型のボクシング

非 null のプリミティブ型ローカル変数は生型を使い、null 許容またはジェネリックではボクシングされます。value class でラッパーを最適化できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
var count = 0 // プリミティブ int
// 可空やコレクション要素はボックス化:
val maybe: Int? = null // Integer
val list = listOf(1, 2) // List<Integer>
// 配列:
val raw = intArrayOf(1) // int[]
val boxed = arrayOf(1) // Integer[]
println(list.size)
}
// プリミティブ型は == null と比較できない
// ボックス化の == と === は差異がある
// ホットパスではボックス化を避ける:
fun sum(raw: IntArray): Long {
var total = 0L
for (n in raw) total += n
return total
}
// value class はインライン化でラッパーオブジェクトを排除

パフォーマンスチューニング

測定してから最適化します。よくある最適化ポイント:データ構造、重複計算、遅延評価、アルゴリズムの計算量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
// 1. コレクションの選定:
val set = setOf("a", "b") // 検索 O(1)
val list = listOf("a", "b") // 検索 O(n)
println(set.contains("a"))
println(list.size)
// 2. 重複計算を避ける:
val size = list.size // ローカルにキャッシュ
println(size)
// 3. 遅延評価で中間コレクションを節約:
val result = (1..1000)
.asSequence()
.filter { it % 2 == 0 }
.take(10)
.toList()
println(result)
}
// まず計測してから最適化、アルゴリズムの複雑度を優先
// 過度の早期最適化をせず、まずベンチマークを取る

オブジェクトプール

高並行シナリオではオブジェクトを再利用し頻繁な割り当てを避けます。プールオブジェクトはスレッド安全と返却前の状態クリアが必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Connection
object Pool {
private val available = ArrayDeque<Connection>()
fun acquire(): Connection =
available.removeLastOrNull() ?: Connection()
fun release(c: Connection) {
if (available.size < 10) {
available.addLast(c)
}
}
}
fun main() {
val conn = Pool.acquire()
// 使用中...
Pool.release(conn)
}
// オブジェクトプールは高コストなオブジェクト向け:接続、スレッド、バッファ
// 返却前に状態をクリアする
// プールが満杯なら破棄し、メモリ膨張を避ける

パフォーマンス分析

JFR、VisualVM などのツールで CPU とメモリを分析します。ヒープダンプでメモリリークを特定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// JVM のよく使うプロファイリングツール:
// 1. JFR:java -XX:StartFlightRecording=...
// 2. VisualVM:GUI でヒープと CPU を確認
// 3. jcmd:jcmd <pid> GC.heap_dump
// コマンドラインで手早く確認:
// jps Java プロセスを一覧
// jstat -gc <pid> GC の状況を確認
// メモリリークの特定:
// 1. ヒープダンプ取得:jmap -dump:live,file=h.bin <pid>
// 2. MAT / VisualVM で支配木を分析
// 3. 静的コレクションが無限に増えないか確認
// よくあるリーク:静的キャッシュ、リスナー未登録解除
// コード内で計測:
fun main() {
val start = System.nanoTime()
// ビジネスコード...
println(System.nanoTime() - start)
}

10.オブジェクト指向

クラス、コンストラクタ、プロパティ、継承、インターフェース、ジェネリック、data class、シールドクラス。

クラス定義

class でクラスを宣言し、プロパティは主コンストラクタかクラス本体に置けます。クラスは既定で final で継承できません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
var name: String = ""
var age: Int = 0
fun introduce() {
println("我是 $name$age 岁")
}
}
fun main() {
val p = Person()
p.name = "Alice"
p.age = 30
p.introduce()
}
// クラスは既定で final、継承には open が必要
// プロパティは初期値か初期化が必要
// getter/setter は自動生成される

コンストラクタ

主コンストラクタはクラスヘッダで宣言し、引数を直接プロパティにできます。init ブロックは主コンストラクタの後に実行されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User(
val name: String,
val age: Int = 0, // デフォルト値
) {
init {
println("初始化 $name")
}
// セカンダリコンストラクタ:
constructor(name: String, age: Int, email: String) :
this(name, age) {
println(email)
}
}
fun main() {
val u = User("Alice", 30)
val u2 = User("Bob")
val u3 = User("Cathy", 20, "[email protected]")
}
// 主コンストラクタの引数に val/var を付けるとプロパティになる
// 修飾子なしなら単なるコンストラクタ引数
// init ブロックは宣言順に実行される

プロパティ

プロパティは getter/setter を自動生成します。アクセサはカスタマイズでき、field はバッキングフィールドを指します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Circle {
var radius: Double = 1.0
// カスタム getter:
val area: Double
get() = Math.PI * radius * radius
// カスタム setter:
var diameter: Double
get() = radius * 2
set(value) {
radius = value / 2
}
}
fun main() {
val c = Circle()
c.radius = 10.0
println(c.area) // 314.159...
c.diameter = 20.0 // setter を通る
println(c.radius) // 10.0
}
// プライベート setter:var x set private set
// プロパティはアクセサであり、フィールドではない

継承とオーバーライド

open クラスは継承可能で、open メンバはオーバーライド可能です。override でオーバーライドをマークし、super で親クラスにアクセスします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class Animal(val name: String) {
open fun speak() = "$name 叫了一声"
}
class Dog(name: String) : Animal(name) {
override fun speak() = "$name: 汪汪"
}
class Cat(name: String) : Animal(name) {
override fun speak() = super.speak() + ",喵"
}
fun main() {
val a: Animal = Dog("旺财")
println(a.speak()) // ポリモーフィズム
val cat = Cat("咪咪")
println(cat.speak())
}
// クラスは既定で final、継承するには open が必要
// override メンバは既定で open
// 構築順序:親クラスの init が先に実行

インターフェース

インターフェースは振る舞いの契約を定義し、デフォルト実装と抽象メンバを持てます。クラスは複数のインターフェースを実装できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Drawable {
fun draw()
fun resize(factor: Double) { // デフォルト実装
println("resize x$factor")
}
}
interface Clickable {
fun click()
}
class Button : Drawable, Clickable {
override fun draw() = println("绘制按钮")
override fun click() = println("点击")
}
fun main() {
val btn = Button()
btn.draw()
btn.resize(2.0) // デフォルト実装
}
// インターフェースはプロパティを宣言できる:val name: String
// インターフェースは状態を持てない(フィールド値を保存不可)
// 同じシグネチャのメソッドは override で曖昧さを解消

抽象クラス

abstract class は部分実装を定義し、abstract メンバはサブクラスで実装されます。インターフェースと異なり、抽象クラスは状態を保持できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Shape {
abstract fun area(): Double
fun describe() = "面积: ${area()}"
}
class Square(val side: Double) : Shape() {
override fun area() = side * side
}
class Circle(val r: Double) : Shape() {
override fun area() = Math.PI * r * r
}
fun main() {
val shapes = listOf(Square(2.0), Circle(1.0))
shapes.forEach { println(it.describe()) }
}
// 抽象クラスはプロパティとコンストラクタを持てる
// インターフェースの方が柔軟なので優先する
// 抽象メンバはサブクラスで実装する必要がある

data class

data class は equals/hashCode/toString/copy/componentN を自動生成し、値オブジェクトに適しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data class Point(val x: Int, val y: Int)
fun main() {
val p1 = Point(1, 2)
val p2 = Point(1, 2)
println(p1 == p2) // true 構造的等価
println(p1.hashCode() == p2.hashCode())
println(p1.toString()) // Point(x=1, y=2)
val p3 = p1.copy(y = 5)
println(p3) // Point(x=1, y=5)
val (x, y) = p1 // 分解
println("$x,$y")
}
// 主コンストラクタは最低 1 つの val/var 引数が必要
// copy は浅いコピー
// DTO、結果オブジェクト、比較キーに適する

シールドクラス

sealed class はサブクラスを同じパッケージ/モジュールに制限し、when 分岐を網羅できます。制限された階層構造を表現します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sealed class Result {
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
}
fun handle(result: Result): String = when (result) {
is Result.Success -> "成功: ${result.data}"
is Result.Failure -> "失败: ${result.error}"
}
fun main() {
println(handle(Result.Success("ok")))
}
// サブクラスは同じパッケージ/モジュール内にある必要がある
// when が網羅的なら else は不要
// サブクラスを増やすと when でコンパイラが警告
// 列挙型より柔軟で、データを保持できる

シングルトンオブジェクト

object はシングルトンを宣言し、スレッドセーフに遅延初期化されます。プログラム全体でインスタンスは 1 つだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object Logger {
private var count = 0
fun log(msg: String) {
count++
println("[$count] $msg")
}
}
fun main() {
Logger.log("启动")
Logger.log("结束")
}
// object はシングルトン:グローバルに唯一のインスタンス
// 遅延ロード、初回アクセス時に生成
// スレッドセーフ
// コンストラクタなし
// 用途:ユーティリティ、設定、レジストリ

コンパニオンオブジェクト

companion object はクラスレベルのメンバを提供し、Java の static に似ています。@JvmStatic で Java に公開できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Calculator {
companion object {
const val VERSION = "1.0"
fun create() = Calculator()
// 静的メソッドとして公開:
// @JvmStatic fun create() = Calculator()
}
}
fun main() {
println(Calculator.VERSION)
val calc = Calculator.create()
println(calc)
}
// コンパニオンオブジェクトはクラスの一部
// アクセス:クラス名.メンバ
// コンパニオンオブジェクト自体に名前を付けられる:
// companion object Factory { }
// クラスに持てるコンパニオンオブジェクトは 1 つ

ジェネリック

ジェネリックは型をパラメータ化します。in/out で共変・反変を宣言し、ジェネリック関数と型制約をサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Box<T>(val value: T)
fun <T> List<T>.firstOr(default: T): T =
if (isEmpty()) default else first()
fun main() {
val box = Box(42) // Box<Int>
val s = Box("hello") // Box<String>
println(listOf(1, 2).firstOr(0))
println(emptyList<Int>().firstOr(-1))
println(box.value + s.value.length)
}
// 型制約:
fun <T : Comparable<T>> bigger(a: T, b: T): T =
if (a > b) a else b
// 共変 out / 反変 in:
interface Producer<out T> { fun get(): T }
interface Consumer<in T> { fun put(item: T) }
// スター投影:List<*>

委譲

by キーワードはインターフェース委譲とプロパティ委譲を実現します。lazy/observable でプロパティを管理し、合成を簡素化します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import kotlin.properties.Delegates
interface Base {
fun print()
}
class BaseImpl : Base {
override fun print() = println("Base 实现")
}
class Derived(b: Base) : Base by b // 委譲
fun main() {
val d = Derived(BaseImpl())
d.print() // BaseImpl に委譲
}
// 委譲プロパティ:
var observable: String by Delegates.observable("init") {
_, old, new -> println("$old -> $new")
}
// 遅延委譲:
val heavy: String by lazy { "expensive" }
// map への委譲(動的プロパティ):
class Person(map: Map<String, Any>) {
val name: String by map
}

11.エラー処理

try 式、カスタム例外、runCatching、Result、require/check、リソース管理。

try 式

try は式で、catch の値を返せます。catch は例外型をマッチし、finally は必ず実行されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun parse(text: String): Int = try {
text.toInt()
} catch (e: NumberFormatException) {
-1
}
fun risky() {
// 失敗する可能性のある呼び出しをシミュレート
}
fun main() {
println(parse("42")) // 42
println(parse("abc")) // -1
// finally ブロック:
try {
risky()
} catch (e: Exception) {
println("捕获: ${e.message}")
} finally {
println("始终执行")
}
}
// 複数 catch は例外型でマッチ

例外を投げる

throw は例外オブジェクトを投げます。カスタムメッセージと例外チェーン。require/check は引数検証の慣用句です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun validate(age: Int) {
if (age < 0) {
throw IllegalArgumentException("年龄不能为负")
}
}
fun main() {
try {
validate(-1)
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
// カスタムメッセージを投げる:
// throw IllegalStateException("状态非法")
// 原因チェーン付き:
// throw RuntimeException("上层错误", Throwable("底层"))
// 引数検証のイディオム:
// require(age >= 0) { "年龄必须非负" }
// requireNotNull(text)
// check(state == "ready") { "未就绪" }

カスタム例外

Exception または RuntimeException を継承してカスタム例外を定義します。追加フィールドを持てます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ValidationError(
val field: String,
message: String,
) : Exception("字段 $field 无效: $message")
fun parseUser(input: String) {
if (input.isBlank()) {
throw ValidationError("input", "不能为空")
}
}
fun main() {
try {
parseUser("")
} catch (e: ValidationError) {
println("字段: ${e.field}")
println("消息: ${e.message}")
}
}
// Exception か RuntimeException を継承
// Kotlin に検査例外の制限はない
// 例外に data class を付けると等価比較が簡単

runCatching

runCatching は例外を捕捉して Result を返します。try-catch の関数型代替手段です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun risky() = 42
fun main() {
val result = runCatching { "42".toInt() }
println(result.getOrNull()) // 42
val failed = runCatching { "x".toInt() }
println(failed.getOrNull()) // null
// デフォルト値:
val n = runCatching { "x".toInt() }
.getOrDefault(0)
println(n)
// 分岐処理:
runCatching { risky() }
.onSuccess { println("成功: $it") }
.onFailure { println("失败: ${it.message}") }
// 変換:
val len = runCatching { "hi" }
.map { it.length }
.getOrDefault(-1)
println(len)
}
// 注意:runCatching は CancellationException も捕捉する

Result 型

Result<T> は成功値または失敗例外をカプセル化し、map/onSuccess などをサポートします。関数の戻り型としてよく使われます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun divide(a: Int, b: Int): Result<Int> =
runCatching { a / b }
fun main() {
val ok = divide(10, 2)
val bad = divide(10, 0)
println(ok.getOrNull()) // 5
println(bad.exceptionOrNull()?.message)
// チェーン操作:
divide(10, 2)
.map { it * 2 }
.onSuccess { println("结果 $it") }
.onFailure { println("出错") }
// アンラップ(失敗時は例外を投げる):
val v = divide(10, 2).getOrThrow()
println(v)
}
// Result は単一値のラップに使う
// コレクションでは fold で複数の Result を集約

リソース管理

use 拡張は Closeable/AutoCloseable リソースを自動で閉じ、Java の try-with-resources と同様です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.File
fun readFirstLine(path: String): String? =
File(path).bufferedReader().use { reader ->
reader.readLine()
}
fun main() {
println(readFirstLine("data.txt"))
}
// use は finally で自動クローズを保証:
// File("f").bufferedReader().use { it.readText() }
// useLines はストリームで 1 行ずつ:
fun first10(path: String) {
File(path).useLines { lines ->
lines.take(10).forEach { println(it) }
}
}
// カスタムリソース:
class Resource : AutoCloseable {
override fun close() = println("关闭")
}
fun demo() {
Resource().use { println("使用中") }
}

require と check

require は引数検証、check は状態検証、error は能動的失敗です。失敗時は例外を投げメッセージを出力します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun setAge(age: Int) {
require(age in 0..150) {
"年龄超出范围: $age"
}
}
fun main() {
// require 失敗時は IllegalArgumentException を投げる
setAge(30)
// check 失敗時は IllegalStateException を投げる:
check(true) { "状态正常" }
// error() で能動的に失敗:
val value: String? = "ok"
val v = value ?: error("值为空")
println(v)
}
// assert はデバッグ時のみ有効:
// assert(false) { "仅测试" }
// アサーション有効化には -ea 引数

Nothing と失敗

Nothing は決して戻らないことを表し、todo/error などの未実装または必ず失敗するパスに使います。型システムが制御フローを理解します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
val x = "a"
val result = when (x) {
"a" -> 1
else -> fail("未知输入")
}
println(result) // 1
}
// Nothing は任意の型のサブタイプ:
fun nullable(): String? = fail("空的")
// 未実装のプレースホルダ:
fun notImplemented(): Nothing {
throw NotImplementedError("尚未实现")
}
// error() は Nothing を返す:
// val cfg = mapOf("port" to 8080)
// val port = cfg["port"] ?: error("缺少端口")

12.入出力

ファイルの読み書き、Path API、標準入出力、JSON シリアライズ、ファイル操作。

ファイル読み込み

readText はファイル全体を文字列として読み込み、readBytes はバイトを読みます。文字化け防止のため文字集合を指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.File
fun main() {
val text = File("data.txt").readText()
println(text)
// 文字セット指定:
val utf8 = File("data.txt")
.readText(Charsets.UTF_8)
println(utf8.length)
// バイト読み込み:
val bytes = File("image.png").readBytes()
println(bytes.size)
}
// 相対パスは作業ディレクトリ基準
// ファイルがなければ FileNotFoundException を投げる
// 読み込み失敗は runCatching で包んで処理

ファイル書き込み

writeText は上書き、appendText は追記、writeBytes はバイト書き込みです。ディレクトリは先に存在する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.File
fun main() {
File("out.txt").writeText("Hello, Kotlin!")
// 追記:
File("log.txt").appendText("新行\n")
// バイト書き込み:
File("data.bin").writeBytes(byteArrayOf(1, 2))
// 文字セット指定:
File("out.txt").writeText("中文内容", Charsets.UTF_8)
// 複数行:
File("out.txt").writeText(
listOf("a", "b", "c").joinToString("\n")
)
}
// writeText は既定で元の内容を上書き
// ディレクトリがなければ IOException を投げる
// 大きなファイルはバッファ付きストリームで分割書き込み

行単位読み込み

readLines は全行を読み、useLines は遅延ストリームで大ファイルを行単位で処理します。forEachLine は手軽な反復です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.File
fun main() {
// 全行(小ファイル):
val lines = File("data.txt").readLines()
lines.forEach { println(it) }
// 遅延で 1 行ずつ(大きなファイル):
File("big.log").useLines { seq ->
seq.filter { "ERROR" in it }
.take(5)
.forEach { println(it) }
}
}
// useLines はファイルを自動クローズ
// 大きなファイルを readLines で一度に読まない
// forEachLine は手軽な 1 行ずつの走査

Path API

java.nio.file.Path はパスを扱い、Files は読み書きとディレクトリ操作を提供します。パス結合はクロスプラットフォームです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.nio.file.Files
import java.nio.file.Path
fun main() {
val base = Path.of("data")
val file = base.resolve("note.txt")
println(file) // data\note.txt
println(Files.exists(file))
println(file.fileName)
println(file.parent)
// ディレクトリ作成:
Files.createDirectories(Path.of("a/b/c"))
// コピーと移動:
Files.copy(file, Path.of("backup.txt"))
Files.move(file, Path.of("new.txt"))
}
// Path.of はパス区切りを自動でプラットフォームに合わせる
// Files.list は use でストリームを閉じる
// Files.readString / writeString の方が簡単

標準入力

readln は 1 行を読み、readlnOrNull は null 許容で読みます。読み取り後に必要な型へ手動で変換します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
println("输入名字:")
val name = readln() // 1 行読む
println("你好, $name")
// 空の可能性がある読み込み:
val line = readlnOrNull() ?: ""
// 数値を読む(手動変換):
val age = readln().toIntOrNull() ?: 0
println("年龄: $age")
}
// 一括行入力:
// generateSequence { readlnOrNull() }
// .take(5).forEach { println(it) }
// readln は EOF で例外を投げる、readlnOrNull の方が安定

標準出力

println/print は標準出力に出力し、println は自動的に改行します。エラーストリームにも出力可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
println("hello") // 改行付き
print("world") // 改行なし
print("!\n")
val name = "Kotlin"
println("Hello, $name")
// フォーマット:
println("%5.2f".format(3.14159)) // 3.14
// エラーストリームへ出力:
System.err.println("错误信息")
}
// println は毎回改行を追加
// 大量出力は BufferedWriter でバッファリング
// PrintWriter の方が Java らしい書き方

JSON シリアライズ

kotlinx.serialization は公式のシリアライズライブラリで、@Serializable でコーデックを生成します。Jackson も代替として使用できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 依存:
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class User(val name: String, val age: Int)
fun main() {
val json = Json { prettyPrint = true }
val u = User("Alice", 30)
val text = json.encodeToString(u)
println(text)
val back = json.decodeFromString<User>(text)
println(back)
}
// Gradle で kotlin serialization プラグインを有効化する必要がある
// 未知フィールドの無視:Json { ignoreUnknownKeys = true }

ファイル操作

存在判定、削除、名前の変更、サイズ、権限。walkTopDown でディレクトリを再帰的に走査します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File
fun main() {
val f = File("data.txt")
println(f.exists())
println(f.isFile)
println(f.isDirectory)
println(f.length()) // バイトサイズ
println(f.lastModified())
// 削除と名前変更:
f.delete()
f.renameTo(File("new.txt"))
// 作成:
f.createNewFile()
// 一時ファイル:
val tmp = File.createTempFile("prefix", ".tmp")
tmp.deleteOnExit()
// 再帰走査:
File(".").walkTopDown()
.filter { it.extension == "kt" }
.forEach { println(it) }
}

13.よくある落とし穴

日常的な Kotlin 開発で一番踏みやすい落とし穴と正しい書き方、BAD/GOOD 対比。

!! の濫用

!! は null のとき NPE を投げるため避け、安全呼び出し、Elvis、事前の検証で代替してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
// BAD:null でそのままクラッシュ
// val len = maybe!!.length
// GOOD:Elvis でデフォルト値
val len = maybe?.length ?: 0
// GOOD:事前検証でエラーを明示
val text = maybe ?: error("配置缺失")
// GOOD:安全呼び出ししてから処理
maybe?.let { println(it.length) }
println(len)
println(text)
}
// !! は論理的に非 null が保証される場合のみ使う
// チームレビューで !! の濫用を止めるべき

スマートキャスト失敗

可変プロパティや委譲プロパティではスマートキャストが失敗します。代入後に再チェックするかローカル変数に取り出してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Box(var value: Any? = null)
// BAD:var プロパティはスマートキャスト不可
fun bad(b: Box) {
// println(b.value.length) コンパイルエラー
}
// GOOD:先にローカル val へ取り出す
fun good(b: Box) {
val v = b.value
if (v is String) {
println(v.length)
}
}
fun main() {
good(Box("hello"))
}
// 理由:var は並行で変更される可能性がある
// 委譲プロパティもスマートキャスト不可
// スマートキャストの前提:チェック後に変化しないこと

== と ===

== は構造的等価、=== は参照の同一性を比較します。オブジェクトとプリミティブ型を区別してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {
val s1 = "abc"
val s2 = "abc"
// BAD:=== で内容比較、定数プール依存
println(s1 === s2) // true の場合も false の場合も
// GOOD:構造比較は ==
println(s1 == s2) // true
}
// BAD:通常クラスは equals をオーバーライドしない
class Point(val x: Int)
fun badEq() {
println(Point(1) == Point(1)) // false
}
// GOOD:data class は自動で equals
fun goodEq() {
println(Point2(1) == Point2(1)) // true
}
data class Point2(val x: Int)
// 数値のボックス化比較:
// val a: Int? = 100
// val b: Int? = 100
// a == b // true、構造的に等しい

null 許容の連結

null 許容値を文字列テンプレートに埋め込むと、null は "null" と表示されます。null を明示的に処理する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val name: String? = null
// BAD:"null" という文字列を出力
println("欢迎 $name")
// GOOD:Elvis でデフォルト値
println("欢迎 ${name ?: "访客"}")
// GOOD:?. で安全に変換してからフォールバック
println("欢迎 ${name?.uppercase() ?: ""}")
}
// BAD:連結すると null.toString になる
// val msg = "用户:" + name // ユーザー:null
// GOOD:
// val msg = "用户:${name ?: "未知"}"
// テンプレートでプロパティアクセスするなら波括弧

テンプレートの null 許容トラップ

$a.b はプロパティアクセス a.b として解釈されます。null 許容オブジェクトのプロパティアクセスは ${a?.b} を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data class User(val nick: String?)
fun main() {
val u = User(null)
// BAD:$u.nick がプロパティアクセスと解釈される
// println("$u.nick") 構文の曖昧さ
// GOOD:式に波括弧
println("${u.nick}") // null
// BAD:null のまま表示すると読みにくい
println("昵称: ${u.nick}")
// GOOD:Elvis でフォールバック
println("昵称: ${u.nick ?: "未设置"}")
}
// 単一変数テンプレート:$name で正しい
// ドット付きアクセスは波括弧必須:${u.nick}

配列共変のトラップ

Array<String> は Array<Any> に代入でき、実行時に ArrayStoreException が発生する可能性があります。List のほうが安全です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
// BAD:配列の共変で実行時クラッシュ
val strings = arrayOf("a", "b")
val anys: Array<Any> = strings
// anys[0] = 1 ArrayStoreException を投げる
// GOOD:List を使う(共変は安全)
val list: List<Any> = listOf("a", "b")
println(list.size)
}
// GOOD:キャストではなく新しい配列を作る
// val safe = strings.map { it as Any }.toTypedArray()
// 理由:JVM 配列は実行時に要素型を保持
// Kotlin は Java 互換のため配列の共変を保持
// 読み取り専用 List は不変なので問題なし

lateinit 未初期化

初期化前にアクセスすると UninitializedPropertyAccessException が投げられます。アクセス前に isInitialized を確認してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Service {
lateinit var client: String
fun start() {
client = "connected"
}
fun ping() {
// BAD:start を呼ばなければクラッシュ
// println(client)
// GOOD:先にチェックしてからアクセス
if (::client.isInitialized) {
println(client)
}
}
}
fun main() {
val s = Service()
s.start()
s.ping()
}
// BAD:空文字で初期化して問題を偽装
// lateinit var s: String = ""
// GOOD:val + by lazy の方が安全
// または可空型 + null チェック

スコープ関数の混同

let/apply/run は戻り値が異なります。誤用すると結果ではなくオブジェクトを返します。用途に応じて選んでください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data class User(var name: String)
fun main() {
val u = User("Alice")
// BAD:apply は結果でなくオブジェクトを返す
val upper = u.apply {
name = name.uppercase()
}
println(upper.name) // ALICE、ただし upper は User
// GOOD:let は Lambda の結果を返す
val upper2 = u.let { it.name.uppercase() }
println(upper2) // ALICE、String
}
// 選択ルール:
// レシーバを返す -> apply / also
// 結果を返す -> let / run / with
// null チェック -> let + ?.

コンパニオンオブジェクトの混同

コンパニオンオブジェクトのメンバはクラスの静的フィールドではありません。Kotlin はコンパニオンオブジェクトで静的セマンティクスを実現し、Java に公開するには @JvmStatic が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Config {
companion object {
val version = "1.0"
fun load() = println("加载")
}
}
fun main() {
// GOOD:Kotlin 内はクラス名で直接アクセス
println(Config.version)
Config.load()
}
// BAD:コンパニオンメンバーを static フィールドと誤解
// Java リフレクションでは静的フィールドを取得できない
// GOOD:@JvmStatic で Java に公開
class Config2 {
companion object {
@JvmStatic
fun load() = println("加载")
}
}
// コンパニオンオブジェクトは依然としてオブジェクトインスタンス
// Java からは Config.Companion.load() でアクセス

読み取り専用ビューの誤解

読み取り専用 List インターフェースはビューに過ぎず、基盤の可変コレクションはまだ変更可能です。可変状態を共有するときはコピーしてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
// BAD:読み取り専用ビューを不変と誤解
val mutable = mutableListOf(1, 2)
val view: List<Int> = mutable
mutable.add(3) // view も変わった!
println(view) // [1, 2, 3]
// GOOD:不変コピー
val frozen = mutable.toList()
println(frozen)
// GOOD:真に不変なら listOf
val fixed = listOf(1, 2)
println(fixed)
}
// マルチスレッド共有コレクション:
// BAD:可変コレクションを共有
// GOOD:不変コレクションかロック
// toList/toSet は新しい不変コピーを生成

14.並行処理とコルーチン

スレッド、コルーチン、async/await、Channel、Flow、相互排他ロック、タイムアウト制御。

スレッドの基礎

Thread と thread{} でスレッドを作成します。join で終了を待ちます。同時並行で共有する状態は同期が必要です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlin.concurrent.thread
fun main() {
// スレッドを作成して起動:
val t = thread(start = true) {
repeat(3) {
println("子线程: $it")
Thread.sleep(100)
}
}
t.join() // 子スレッドの終了を待つ
println("主线程结束")
}
// ExecutorService でスレッドプールを管理:
// val pool = Executors.newFixedThreadPool(4)
// マルチスレッドでの共有可変状態は同期が必要
// Thread.sleep はブロッキング待機

コルーチン入門

launch は軽量コルーチンを起動し、delay はスレッドを占有せずに中断します。runBlocking はコルーチンの完了を待機してブロックします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlinx.coroutines.*
fun main() = runBlocking {
// launch でコルーチン起動(非ブロッキング):
launch {
delay(100) // サスペンド、スレッドを占有しない
println("协程 A")
}
launch { println("协程 B") }
// runBlocking は全コルーチンの完了を待つ
println("顺序执行到这里")
}
// suspend 関数はコルーチン内でのみ呼べる
// delay はコルーチンをサスペンド、Thread.sleep はスレッドをブロック
// 依存:kotlinx-coroutines-core

async と await

async は並行実行して Deferred を返し、await で結果を取得します。awaitAll は複数のタスクを待ちます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlinx.coroutines.*
suspend fun fetch(id: Int): String {
delay(100)
return "数据$id"
}
fun main() = runBlocking {
// async で並行実行、await で結果取得:
val a = async { fetch(1) }
val b = async { fetch(2) }
println(a.await() + b.await()) // データ1データ2
// 並行リスト:
val results = (1..3).map { id ->
async { fetch(id) }
}.awaitAll()
println(results)
}
// Deferred<T> は Future に類似
// async でエラー時は await が例外を投げる
// 大量タスクは map + awaitAll で並行実行

withContext

withContext はディスパッチャを切り替えてブロッキングタスクを実行します。IO タスクは Dispatchers.IO、計算は Default を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import kotlinx.coroutines.*
fun main() = runBlocking {
// IO 集約タスクを IO ディスパッチャへ:
val data = withContext(Dispatchers.IO) {
Thread.sleep(10) // ブロッキング IO をシミュレート
"从磁盘读取"
}
println(data)
// Default:CPU 集約計算
val sum = withContext(Dispatchers.Default) {
(1..1000).sum()
}
println(sum)
}
// Dispatchers.IO:ネットワーク/ディスク IO タスク
// Dispatchers.Default:計算集約タスク
// Dispatchers.Main:Android メインスレッド
// withContext は最後の式の結果を返す

Channel

Channel はコルーチン間でデータを転送し、send/receive は中断関数です。バッファリングとクローズをサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
fun main() = runBlocking {
val channel = Channel<Int>(capacity = 5)
// 生産者:
launch {
for (i in 1..3) {
channel.send(i)
println("发送 $i")
}
channel.close()
}
// 消費者:
for (v in channel) {
println("接收 $v")
}
}
// バッファなし:送受信が同期してペアになる
// バッファあり:capacity でバッファサイズ指定
// イテレーションは channel クローズ後に終了
// 多対多は broadcast を使う

Flow データストリーム

Flow は非同期コールドストリームで、collect 時に実行されます。map/filter などの演算子と背圧をサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun countDown(): Flow<Int> = flow {
for (i in 3 downTo 1) {
emit(i) // 要素を発射
delay(100)
}
}
fun main() = runBlocking {
countDown()
.map { "第$it 秒" }
.onEach { println(it) }
.collect()
// flowOf / asFlow で簡単に作成:
listOf(1, 2).asFlow().collect { println(it) }
}
// Flow はコールドストリーム:collect で初めて実行
// 背圧をサポート:消費速度に合わせて発射
// collect はサスペンド関数

相互排他と同期

AtomicInteger クラスはロックなしのカウンタを提供します。コルーチン内では Mutex でクリティカルセクションを保護し、スレッド内では synchronized を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import java.util.concurrent.atomic.AtomicInteger
// 共有カウンター:
val counter = AtomicInteger(0)
fun main() = runBlocking {
// 方法 1:アトミッククラス
val jobs = List(10) {
launch { repeat(1000) { counter.incrementAndGet() } }
}
jobs.forEach { it.join() }
println(counter.get()) // 10000
// 方法 2:コルーチン用ミューテックス
val mutex = Mutex()
var total = 0
launch {
repeat(1000) {
mutex.withLock { total++ } // クリティカルセクション
}
}.join()
println(total)
}
// @Synchronized は通常スレッドのメソッドを同期
// volatile は可視性を保証、原子性は保証しない
// コルーチン内は Mutex、スレッドは synchronized

タイムアウトとキャンセル

withTimeout はコルーチンの実行時間を制限します。job.cancel は協調的キャンセルで、isActive を確認する必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import kotlinx.coroutines.*
fun main() = runBlocking {
// タイムアウト制御:
try {
withTimeout(500) {
repeat(10) {
delay(200) // 合計 2 秒かかる
}
}
} catch (e: TimeoutCancellationException) {
println("超时了")
}
// コルーチン取り消し:
val job = launch {
while (isActive) {
println("工作中...")
delay(100)
}
}
delay(300)
job.cancel() // 取り消しを要求
println("已取消")
}
// 協調的キャンセル:isActive をチェックする必要がある
// サスペンドポイントはキャンセルに応答し CancellationException を投げる
// リソース整理は finally + withContext(NonCancellable)

15.ネットワークプログラミング

HTTP リクエスト、Ktor Client、JSON 解析、TCP ソケット、HTTP サーバー、WebSocket。

URL 簡単リクエスト

URL.readText は高速な GET です。openConnection でタイムアウトを設定できます。軽量で小さなレスポンスに適しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.net.URL
fun main() {
// 簡単な GET リクエスト:
val html = URL("https://example.com")
.readText()
println(html.take(100))
// タイムアウト付き:
val conn = URL("https://example.com")
.openConnection()
conn.connectTimeout = 3000
conn.readTimeout = 3000
val body = conn.getInputStream().readBytes()
println(body.size)
}
// readText は小さなレスポンス向け
// 本番は HttpClient の方が制御しやすい
// ネットワーク例外の処理に注意

JDK HttpClient

JDK 11+ には HTTP リクエスト送信用の HttpClient が内蔵されています。非同期 sendAsync とレスポンスボディ変換をサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
val client = HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(5))
.build()
val request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.GET()
.build()
val response = client.send(
request, HttpResponse.BodyHandlers.ofString()
)
println(response.statusCode())
println(response.body().take(100))
}
// JDK 11+ 組み込みの HTTP クライアント
// 非同期版は sendAsync で CompletableFuture を返す
// GET/POST/Header/タイムアウト設定をサポート

Ktor Client

Ktor Client はクロスプラットフォームの HTTP ライブラリで、コルーチンと複数エンジンをサポートします。ネットワーク集中タスクに適しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 依存:io.ktor:ktor-client-core
// io.ktor:ktor-client-java
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.runBlocking
suspend fun fetch() {
val client = HttpClient()
val body = client.get("https://example.com")
.bodyAsText()
println(body.take(100))
client.close()
}
fun main() = runBlocking {
fetch()
}
// Ktor Client はクロスプラットフォーム HTTP ライブラリ
// WebSocket、SSE、認証をサポート
// エンジンは変更可能:OkHttp / CIO / Java

レスポンス JSON 解析

HttpClient でレスポンスを取得した後、kotlinx.serialization で JSON を data class にデシリアライズします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
@Serializable
data class Repo(val name: String, val url: String)
fun main() {
val client = HttpClient.newBuilder().build()
val req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/repos/kotlin/kotlinx.serialization"))
.header("Accept", "application/json")
.GET()
.build()
val body = client.send(
req, HttpResponse.BodyHandlers.ofString()
).body()
// JSON を解析:
val repo = Json.decodeFromString<Repo>(body)
println(repo.name)
}
// 先に @Serializable を付けてから decodeFromString
// フィールド名が異なる場合は @SerialName
// 未知フィールドの無視:Json { ignoreUnknownKeys = true }

TCP ソケット

ServerSocket がポートを監視し、accept が接続を受け付けます。Socket はバイトストリームを読み書きします。リソースのクローズに注意してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.net.ServerSocket
import java.net.Socket
fun main() {
// サーバーがポートを監視:
val server = ServerSocket(9999)
println("监听 9999...")
val client = server.accept() // 接続をブロッキング待機
val input = client.getInputStream().bufferedReader()
println("收到: " + input.readLine())
client.close()
server.close()
}
// クライアント接続:
// val s = Socket("localhost", 9999)
// s.getOutputStream().write("hi".toByteArray())
// 本番は NIO / Netty で高並行を処理
// リソースを閉じるのを忘れない(use で自動クローズ)

HTTP サーバー

JDK 内蔵の HttpServer で軽量な HTTP サービスを作成します。createContext でルートハンドラを登録します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sun.net.httpserver.HttpServer
import java.net.InetSocketAddress
fun main() {
val server = HttpServer.create(
InetSocketAddress(8080), 0
)
server.createContext("/hello") { exchange ->
val body = "Hello, Kotlin!".toByteArray()
exchange.responseHeaders.add(
"Content-Type", "text/plain"
)
exchange.sendResponseHeaders(200, body.size.toLong())
exchange.responseBody.use { it.write(body) }
}
server.start()
println("服务已启动: http://localhost:8080/hello")
}
// JDK 組み込みの軽量 HTTP サーバー
// ローカルツールと簡単な API 向け
// 本番は Ktor / Spring Boot を推奨
// 停止:server.stop(0)

REST 設計要点

REST はリソースと HTTP メソッドで操作を表現します。data class でリクエストとレスポンスを記述し、ステータスコードの意味を正確にします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class Todo(
val id: Int,
val title: String,
val done: Boolean = false,
)
// REST 設計の要点:
// GET /todos 一覧
// GET /todos/{id} 詳細
// POST /todos 作成(201 を返す)
// PUT /todos/{id} 全体更新
// DELETE /todos/{id} 削除(204 を返す)
fun main() {
val todos = listOf(Todo(1, "学习 Kotlin"))
println(Json.encodeToString(todos))
}
// ステータスコードの意味は正確に
// エラーは統一されたエラー構造を返す
// ページングパラメータ:page, size

WebSocket

OkHttp は WebSocket クライアントを提供し、onOpen/onMessage コールバックで双方向メッセージを処理します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 依存:com.squareup.okhttp3:okhttp
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
fun main() {
val client = OkHttpClient()
val request = Request.Builder()
.url("wss://echo.websocket.org")
.build()
val ws = client.newWebSocket(request,
object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
webSocket.send("hello")
}
override fun onMessage(webSocket: WebSocket, text: String) {
println("收到: $text")
}
override fun onClosing(ws: WebSocket, code: Int, reason: String) {
ws.close(code, reason)
}
})
Thread.sleep(2000)
ws.close(1000, "再见")
client.dispatcher.executorService.shutdown()
}
// WebSocket は全二重通信
// 切断後の再接続は自前で実装する必要がある
// onMessage は ByteString のバイナリも受け取る

16.日付と時刻

Instant、LocalDate、フォーマット、解析、Duration、Period、タイムゾーン付き日時。

現在のタイムスタンプ

Instant.now は瞬間を記録します。currentTimeMillis はミリ秒を取得し、nanoTime は間隔測定用です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.time.Instant
fun main() {
// 現在時刻:
val now = Instant.now()
println(now)
// エポック秒/ミリ秒:
println(Instant.now().epochSecond)
println(Instant.now().toEpochMilli())
// 旧式ミリ秒タイムスタンプ:
println(System.currentTimeMillis())
println(System.nanoTime())
}
// Instant は瞬間を表し、タイムスタンプ記録に適する
// nanoTime は間隔の計測用、時計時間ではない
// UTC 時刻とローカル時間を区別する

LocalDate

LocalDate は年月日を表し、of/now で作成します。日数の加減、判定、要素の取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDate
fun main() {
val today = LocalDate.now()
println(today)
val date = LocalDate.of(2026, 8, 2)
println(date)
// 計算:
println(date.plusDays(1))
println(date.minusMonths(2))
println(date.plusWeeks(1))
// 判定:
println(today.isAfter(date))
println(today.dayOfWeek)
println(today.dayOfMonth)
println(today.month)
println(today.isLeapYear)
}
// LocalDate は日付のみ、時間もタイムゾーンもない
// 不変でスレッドセーフ

LocalDateTime

LocalDateTime は日付と時刻を含みます。withHour でフィールド調整、plusHours で加減算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.time.LocalDateTime
fun main() {
val now = LocalDateTime.now()
println(now)
val dt = LocalDateTime.of(2026, 8, 2, 15, 30)
println(dt.hour)
println(dt.minute)
println(dt.second)
// 調整:
println(dt.withHour(9))
println(dt.plusHours(5))
// LocalDate との相互変換:
println(dt.toLocalDate())
println(dt.toLocalTime())
}
// LocalDateTime は日付+時間、タイムゾーンなし
// フィールド操作は新しいインスタンスを返す(不変)

フォーマット出力

DateTimeFormatter.ofPattern でカスタムフォーマットを定義します。yyyy-MM-dd HH:mm:ss はよく使われるパターンです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
fun main() {
val now = LocalDateTime.now()
// よく使うパターン:
println(now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
))
println(now.format(
DateTimeFormatter.ofPattern("yyyy/MM/dd")
))
println(now.format(
DateTimeFormatter.ofPattern("HH:mm")
))
// 組み込みフォーマッタ:
println(now.format(DateTimeFormatter.ISO_LOCAL_DATE))
}
// パターン文字:y年 M月 d日 H時 m分 s秒
// フォーマッタはスレッドセーフ、val で再利用可
// 大文字小文字を区別:M は月、m は分

日付の解析

LocalDate.parse が文字列を解析します。カスタムフォーマットには DateTimeFormatter が必要です。失敗時は例外が投げられます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeParseException
fun main() {
// ISO 形式を解析:
println(LocalDate.parse("2026-08-02"))
// カスタム形式:
val fmt = DateTimeFormatter.ofPattern("yyyy/MM/dd")
println(LocalDate.parse("2026/08/02", fmt))
// 安全な解析:
try {
LocalDate.parse("2026-13-40")
} catch (e: DateTimeParseException) {
println("日期格式错误: ${e.message}")
}
}
// 解析失敗は DateTimeParseException を投げる
// 一括解析は検証してから変換
// 入力が信頼できない場合は try で包む

Duration

Duration はナノ秒ベースの時間量を表し、between で差分を計算します。経過時間の計測に適しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.time.Duration
import java.time.Instant
fun main() {
val start = Instant.now()
// ビジネス処理...
val end = Instant.now()
// 経過時間:
val elapsed = Duration.between(start, end)
println(elapsed.toMillis())
println(elapsed.toSeconds())
// 長さを構築:
println(Duration.ofMinutes(5))
println(Duration.ofHours(2).plusMinutes(30))
// 比較:
println(elapsed.compareTo(Duration.ZERO))
}
// Duration はナノ秒ベースの時間量
// 所要時間と時間差の計測に適する
// ofMinutes/ofHours/ofDays で簡単に構築

Period

Period は年/月/日の差を表し、between で年齢を計算します。ofWeeks/ofDays で周期を構築します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.time.LocalDate
import java.time.Period
fun main() {
val born = LocalDate.of(1995, 5, 20)
val today = LocalDate.now()
// 年月日の差:
val age = Period.between(born, today)
println("${age.years}${age.months} 个月")
// 期間を構築:
println(Period.of(1, 6, 0)) // 1 年 6 か月
println(Period.ofDays(30))
// 日付に適用:
println(today.plus(Period.ofWeeks(2)))
}
// Period は日付ベース(年/月/日)
// Duration は時間ベース(時/分/秒)
// 純粋な日数の差は ChronoUnit.DAYS.between

タイムゾーン付き日時

ZonedDateTime はタイムゾーンを含みます。withZoneSameInstant でタイムゾーンを切り替えます。保存には UTC、表示にはローカルに変換します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.time.ZonedDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
fun main() {
// タイムゾーン付き時刻:
val now = ZonedDateTime.now()
println(now)
// タイムゾーン指定:
val tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"))
println(tokyo)
// タイムゾーン切り替え:
val ny = now.withZoneSameInstant(
ZoneId.of("America/New_York")
)
println(ny)
// タイムゾーン付きフォーマット:
println(now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
))
}
// ZonedDateTime = LocalDateTime + ZoneId
// 保存は UTC(Instant)、表示はローカルタイムゾーンへ変換
// サマータイムの切り替えはライブラリが自動処理

17.プロセスとシステム

コマンドライン引数、環境変数、ProcessBuilder による外部コマンド実行、システム情報、シャットダウンハック。

引数と環境

main の args がコマンドライン引数を受け取ります。System.getenv は環境変数、getProperty はシステムプロパティを読みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main(args: Array<String>) {
// コマンドライン引数:
println("参数个数: ${args.size}")
args.forEachIndexed { i, a -> println("$i: $a") }
// 環境変数:
println(System.getenv("PATH"))
println(System.getenv().size)
// システムプロパティ:
println(System.getProperty("user.home"))
}
// 実行:kotlin MainKt 引数1 引数2
// よく使うプロパティ:user.home / os.name / java.version
// プロパティが存在しなければ null を返す

ProcessBuilder

ProcessBuilder は外部コマンドを実行し、directory で作業ディレクトリを設定します。redirectErrorStream でエラーストリームをマージします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.File
fun main() {
// 外部コマンドを実行:
val pb = ProcessBuilder("ls", "-l")
.directory(File("."))
.redirectErrorStream(true)
val process = pb.start()
// 待機して出力を読み取る:
val output = process.inputStream.bufferedReader()
.readText()
println(output)
println("退出码: ${process.waitFor()}")
}
// ProcessBuilder は Runtime.exec より安全
// redirectErrorStream でエラーストリームを統合
// コマンド引数は分けて渡す、文字列連結はしない(インジェクション防止)

終了コード

waitFor は子プロセスの完了をブロック待機します。終了コード 0 が成功、exitProcess は現在のプロセスの終了コードを設定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.File
fun main() {
// 子プロセスを起動:
val pb = ProcessBuilder("cmd", "/c", "echo hi")
val p = pb.start()
// 非ブロッキングチェック:
println("已启动: ${p.isAlive}")
// ブロッキング待機:
val code = p.waitFor()
println("退出码: $code")
}
// 終了コード 0 は成功を表す
// exitProcess(1) は現在のプロセスを即終了
// exitProcess は finally の後始末をスキップする点に注意

子プロセスの出力を取得

inputStream を読んで子プロセスの出力を取得します。waitFor はタイムアウト付きでハングを防ぎ、destroyForcibly は強制終了します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.File
import java.util.concurrent.TimeUnit
fun main() {
// 標準出力をキャプチャ:
val p = ProcessBuilder("java", "-version")
.redirectErrorStream(true)
.start()
val out = p.inputStream.bufferedReader()
.use { it.readText() }
println("输出:\n$out")
// タイムアウト待機:
if (p.waitFor(5, TimeUnit.SECONDS)) {
println("退出码: ${p.exitValue()}")
} else {
p.destroyForcibly()
println("超时,已终止")
}
}
// 大きな出力は行単位で読み、メモリ消費を避ける
// 子プロセスのブロック防止:読み取りと待機を同時に行う
// destroyForcibly はプロセスを強制終了

システム情報

JVM と OS のプロパティを読み取ります。Runtime でメモリを確認できます。ログと診断に使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// JVM 情報:
println("Java: ${System.getProperty("java.version")}")
println("厂商: ${System.getProperty("java.vendor")}")
println("系统: ${System.getProperty("os.name")}")
println("架构: ${System.getProperty("os.arch")}")
// メモリ:
val runtime = Runtime.getRuntime()
println("最大内存: ${runtime.maxMemory() / 1024 / 1024}MB")
println("已用内存: ${(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024}MB")
// 作業ディレクトリ:
println("工作目录: ${System.getProperty("user.dir")}")
}
// プロセッサコア数:
// Runtime.getRuntime().availableProcessors()
// 本番モニタリングは jcmd / JMX

シャットダウンハック

addShutdownHook で JVM 終了時に実行されるタスクを登録します。リソースのクリーンアップと状態の保存に使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlin.concurrent.thread
fun main() {
// シャットダウンフックを登録:
Runtime.getRuntime().addShutdownHook(
thread(start = false) {
println("清理资源...")
// 状態保存、接続クローズ
}
)
// ビジネスロジック:
println("程序运行中")
Thread.sleep(2000)
println("退出")
}
// フックは JVM の正常終了時に実行
// すべての強制終了シナリオで実行されるとは限らない
// kill -9 ではフックが発火しない

リアルタイム出力読み込み

子プロセスの出力ストリームを行単位で読み、進捗をリアルタイムに処理します。useLines は自動でストリームを閉じます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.File
fun main() {
// 子プロセスの出力を分割読み取り:
val p = ProcessBuilder(
"ping", "-n", "2", "127.0.0.1"
)
.redirectErrorStream(true)
.start()
// 行単位でリアルタイム読み取り:
p.inputStream.bufferedReader().useLines { lines ->
lines.forEach { println("进度: $it") }
}
println("退出码: ${p.waitFor()}")
}
// リアルタイム読み取りでバッファあふれを防止
// useLines は入力ストリームを自動クローズ
// 大量データは行単位処理でメモリに優しい

プロセス終了

destroy は穏やかな終了、destroyForcibly は強制終了です。タイムアウト内に終わらなければ強制終了します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.File
import java.util.concurrent.TimeUnit
fun main() {
val p = ProcessBuilder(
"ping", "127.0.0.1", "-t"
)
.redirectErrorStream(true)
.start()
// タイムアウト後に終了:
val done = p.waitFor(3, TimeUnit.SECONDS)
if (!done) {
println("超时,终止进程")
p.destroy() // 穏やかな終了
p.waitFor()
println("退出码: ${p.exitValue()}")
}
}
// destroy は終了シグナルを送り、子プロセスが応答できる
// destroyForcibly は強制終了、子プロセスに後始末の機会なし
// Windows は taskkill /F でプロセスのツリーを強制終了

18.正規表現

Regex の基礎、マッチ検索、キャプチャグループ、置換・分割、よく使うパターン、フラグオプション。

Regex の基礎

Regex で正規表現オブジェクトを作成します。matches は全体マッチ、containsMatchIn は含有判定、matchEntire は文字列全体マッチです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
// 正規表現を作成:
val email = Regex("^\\w+@\\w+\\.\\w+$")
println(email.matches("[email protected]")) // true
println(email.matches("not-an-email")) // false
// containsMatchIn で含むか判定:
println(Regex("\\d+").containsMatchIn("有 123 个"))
// matchEntire で全体一致:
val num = Regex("-?\\d+\\.?\\d*")
println(num.matchEntire("3.14") != null)
}
// Regex はスレッドセーフ、val で再利用可
// バックスラッシュのエスケープに注意:\\d は \\d を表す
// raw string """ を使うとエスケープを減らせる

リテラルとオプション

Regex.escape は特殊文字をエスケープします。IGNORE_CASE は大小文字無視、MULTILINE は複数行モードです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import kotlin.text.RegexOption
fun main() {
// リテラル一致(特殊文字をそのまま扱う):
val literal = Regex.escape("a.b*c")
println(literal) // a\\.b\\*c
println(Regex(literal).matches("a.b*c")) // true
// 大文字小文字を区別しない:
val ci = Regex("kotlin", RegexOption.IGNORE_CASE)
println(ci.matches("KOTLIN")) // true
// オプションの組み合わせ:
val multi = Regex("^a$", setOf(
RegexOption.MULTILINE,
RegexOption.IGNORE_CASE,
))
println(multi.matches("A"))
}
// メタ文字:. * + ? ( ) [ ] { } ^ $ |
// 特殊文字をマッチさせる前に Regex.escape
// オプション:IGNORE_CASE / MULTILINE / DOT_MATCHES_ALL

マッチ検索

find は最初のマッチを返し、findAll は全マッチを返します。MatchResult は値、グループ、範囲にアクセスできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "id=42 和 id=17 和 id=8"
val re = Regex("id=(\\d+)")
// 最初のマッチ:
val first = re.find(text)
println(first?.value) // id=42
println(first?.groupValues) // [id=42, 42]
// 全マッチ:
val all = re.findAll(text)
all.forEach { m -> println(m.value) }
// 範囲:
println(first?.range) // 0..4
}
// find は最初の MatchResult を返す
// findAll はシーケンスを返す(遅延)
// groupValues[0] は全体、[1..] はグループ

キャプチャグループ

括弧はキャプチャグループを定義します。groupValues はインデックスで取得し、名前付きグループは groups["name"] を使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "2026-08-02 15:30:00"
val re = Regex("(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})")
val m = re.find(text)!!
// グループから値を取得:
println(m.groupValues[1]) // 2026
println(m.groupValues[2]) // 08
// 名前付きグループ:
val named = Regex("(?<year>\\d{4})-(?<month>\\d{2})")
val nm = named.find("2026-08")!!
println(nm.groups["year"]?.value) // 2026
println(nm.groups["month"]?.value) // 08
}
// 非キャプチャグループ:(?:...)
// 怠惰マッチ:(.*?)
// 分解マッチ:val (y, m, d) = m.destructured

正規表現置換

replace はすべてのマッチを置換し、replaceFirst は最初のマッチのみ置換します。置換クロージャはキャプチャグループにアクセスできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val text = "2026-08-02 15:30"
// マッチした内容を置換:
println(Regex("\\d+").replace(text, "#"))
// 最初の 1 件のみ置換:
println(Regex("\\d+").replaceFirst(text, "X"))
// マッチ内容から置換を生成:
val date = Regex("(\\d{4})-(\\d{2})-(\\d{2})")
println(date.replace(text) { m ->
val (y, mo, d) = m.destructured
"$d/$mo/$y"
})
}
// replace はすべてのマッチを処理
// 置換クロージャからグループ値にアクセスできる
// m.destructured でグループを分解

正規表現分割

split(Regex) は正規表現で文字列を分割します。文字列分割より柔軟で、複数の区切り文字をサポートします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val text = "1, 2;3|4"
// 正規表現での分割:
println(text.split(Regex("[,;|]+")))
println(text.split(Regex("\\s*[,;|]\\s*")))
// キーと値を抽出:
val log = "A:1 B:2 C:3"
println(Regex("(\\w+):(\\d+)").findAll(log)
.map { it.destructured.toList() }
.toList())
}
// split は既定で区切り文字を保持しない
// 正規表現分割は文字列分割より柔軟
// 大きなテキストは findAll で遅延処理

よく使う正規表現

メール、電話番号、IPv4、中国語文字、URL のよく使う正規表現テンプレート。必要に応じて境界を調整します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// メールアドレス:
val EMAIL = Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
// 携帯番号(中国本土):
val CN_PHONE = Regex("^1[3-9]\\d{9}$")
// IPv4:
val IPV4 = Regex("^(\\d{1,3}\\.){3}\\d{1,3}$")
// 中国語文字:
val CHINESE = Regex("[\\u4e00-\\u9fa5]")
// URL:
val URL = Regex("^https?://[^\\s]+")
fun main() {
println(EMAIL.matches("[email protected]"))
println(CN_PHONE.matches("13800138000"))
println(IPV4.matches("192.168.1.1"))
println(CHINESE.containsMatchIn("Hello 世界"))
println(URL.matches("https://example.com/x"))
}
// 複雑なルールは専用ライブラリや状態機械を推奨
// 検証と抽出は分ける:まずマッチ、次にグループ取得
// 正規表現は ReDoS リスクがあるため、長さに注意

フラグオプション

RegexOption 定数はマッチ動作を制御します。インラインフラグ (?i)(?m)(?s) をパターン内に書けます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import kotlin.text.RegexOption
fun main() {
// オプション定数:
println(RegexOption.IGNORE_CASE) // 大文字小文字を無視
println(RegexOption.MULTILINE) // マルチラインモード
println(RegexOption.DOT_MATCHES_ALL) // . が改行にも一致
// インラインフラグ:
val inline = Regex("(?i)abc")
println(inline.matches("ABC"))
// 組み合わせ:
val combined = Regex("^\\w+$", RegexOption.IGNORE_CASE)
println(combined.matches("Hello123"))
}
// (?i) 大文字小文字無視 (?m) 複数行 (?s) 単一行
// フラグはインラインより引数で渡す方が明確
// COMMENT オプションは正規表現内のコメントと空白を許可

19.ビルドと依存関係

Gradle Kotlin DSL、プラグイン、依存関係、テスト、コルーチン、シリアライズライブラリ設定。

Gradle の基礎

build.gradle.kts は Kotlin DSL でビルドを設定します。mainClass で実行エントリを指定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ファイル:build.gradle.kts(Kotlin DSL)
plugins {
kotlin("jvm") version "2.0.21"
application
}
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
application {
mainClass.set("MainKt")
}
// 実行:./gradlew run
// ビルド:./gradlew build
// ディレクトリ構成:
// src/main/kotlin/ ソース
// src/test/kotlin/ テスト
// build/libs/ ビルド成果物

Kotlin プラグイン

kotlin("jvm") で JVM プロジェクトを宣言します。jvmToolchain で JDK バージョンを指定します。Wrapper で Gradle を固定します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// プラグイン宣言:
plugins {
kotlin("jvm") version "2.0.21"
}
// よく使うプラグイン:
// kotlin("jvm") JVM プロジェクト
// kotlin("multiplatform") マルチプラットフォーム
// kotlin("kapt") アノテーション処理
// JDK ツールチェーン設定:
kotlin {
jvmToolchain(21)
}
// Wrapper で Gradle バージョンを固定:
// ./gradlew wrapper --gradle-version 8.10
// マルチモジュールは settings.gradle.kts で include

依存関係設定

implementation/testImplementation/compileOnly などの設定で依存関係のスコープを制御します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dependencies {
// 実行時依存:
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
)
// テスト依存:
testImplementation("junit:junit:4.13.2")
// コンパイル時依存:
compileOnly(
"jakarta.servlet:jakarta.servlet-api:6.0.0"
)
}
// 依存設定の説明:
// implementation 実行時とコンパイル時に見える
// testImplementation テストのみ
// compileOnly コンパイル時のみ(パッケージしない)
// runtimeOnly 実行時のみ
// api 利用側に公開(library プロジェクト)

実行と配布

application プラグインは run/installDist タスクを提供します。run --args でコマンドライン引数を渡します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins {
application
}
application {
mainClass.set("MainKt")
}
// コマンドラインで実行:
// ./gradlew run メインクラスを実行
// ./gradlew run --args="引数1 引数2"
// ./gradlew installDist 配布用スクリプトを生成
// 実行 JVM 引数を指定:
tasks.named<JavaExec>("run") {
jvmArgs("-Xmx512m")
}
// 出力先:build/install/<project>/bin/

テスト

kotlin.test がアサーションを提供します。testImplementation でテストライブラリを導入します。test タスクで全テストを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlin.test.Test
import kotlin.test.assertEquals
// テスト対象関数(src/main/kotlin):
fun add(a: Int, b: Int) = a + b
// テストクラス(src/test/kotlin/CalculatorTest.kt):
class CalculatorTest {
@Test
fun add_returns_sum() {
assertEquals(5, add(2, 3))
}
}
// テスト実行:
// ./gradlew test
// よく使うアサーション:
// assertEquals / assertTrue / assertNull
// assertFailsWith<IllegalArgumentException> { ... }
// テストレポート:build/reports/tests/test/

コルーチン依存関係

kotlinx-coroutines-core がコルーチンコアを提供します。Android/Swing 版で対応するメインスレッドディスパッチャを提供します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0"
)
// Android や JVM Swing のメインスレッド:
implementation(
"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
)
}
// 各モジュール:
// kotlinx-coroutines-core コア(コルーチン、Flow、Channel)
// kotlinx-coroutines-android Android Dispatchers.Main
// kotlinx-coroutines-swing Swing メインスレッド
// コアの使い方:
import kotlinx.coroutines.*
fun demo() = runBlocking {
launch { delay(100); println("完成") }
}
// バージョンは Kotlin バージョンと互換が必要

シリアライズ依存関係

kotlin plugin.serialization プラグインと kotlinx-serialization-json ライブラリで JSON エンコード/デコードを実現します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
plugins {
kotlin("jvm") version "2.0.21"
kotlin("plugin.serialization") version "2.0.21"
}
dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3"
)
}
// 使い方:
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class User(val name: String, val age: Int)
fun main() {
println(Json.encodeToString(User("A", 1)))
}
// シリアライゼーションプラグインが自動でコーデックを生成
// プラグインと実行時ライブラリを両方追加する必要がある
// 他の形式:kotlinx-serialization-protobuf など

パッケージと公開

distribution プラグインが配布可能なアーカイブを生成します。publishToMavenLocal でローカルリポジトリに公開します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 配布パッケージを生成:
plugins {
application
distribution
}
application {
mainClass.set("MainKt")
}
// 実行:
// ./gradlew distTar tar 圧縮パッケージを生成
// ./gradlew distZip zip 圧縮パッケージを生成
// ./gradlew installDist build/install に展開
// 成果物には起動スクリプトと lib/ の依存が含まれる
// ローカル Maven リポジトリに公開:
// ./gradlew publishToMavenLocal
// maven-publish プラグイン設定:
plugins {
`maven-publish`
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは Kotlin 2.0 の自己完結型クイックリファレンスで、実プロジェクトでの使用の約 80% をカバーする言語コアと最もよく使う標準ライブラリを扱います。現代的な慣用句に重点を置いています:null 安全(null 許容型 ?、安全呼び出し ?.、エリビス ?:)、data class とシールドクラス、スコープ関数、拡張関数、そしてコルーチンと Flow による非同期並行モデルです。権威あるリファレンスは公式 Kotlin ドキュメントと『Kotlin in Action』を参照してください。 19 のセクションはそれぞれ 1 つのテーマに焦点を当てています —— 最初のプログラムからコルーチン、null 安全、よくある落とし穴まで。各セクションは 8~14 のサブトピック(5~20 行)に分割され、合計約 160 トピックです。コードスニペットは意図的に短く自己説明的で、コメントは簡体字中国語です。 すべての処理はブラウザ内で行われます —— アップロードもトラッキングもありません。このページは GuruToolkit 無料開発者ツールコレクションの一部です。スニペットは自由に利用でき、いかなる保証もありません。

バージョン 2.1.0