本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

Kotlin 速查 — 簡明參考

Kotlin 2.0 語法、空安全、協程與最常用標準庫速查手冊,覆蓋約 80% 日常場景。

Kt

Kotlin Kotlin 2.0

JVM (kotlinc) / Multiplatform · OO · 函數式 · 多平台 · 靜態 · 強類型 · 推斷

學習路徑

先學會 kotlinc 編譯運行與 fun main → 掌握變量、類型與流程控制 → 理解空安全(可空類型 ?、安全調用 ?.、Elvis ?:)與 data class → 用 lambda、擴展函數與集合函數式操作 → 深入類、接口與泛型 → 用 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 引入其他包類型。import 未使用不報錯,與 Java 不同。

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 表示成功,非 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
// 國內建議配置阿里雲 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)
}

顯式類型

顯式聲明類型提升可讀性,也是空安全與泛型的常見需求。類型寫在變量名後,用冒號分隔。

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 六種數字類型,後綴 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
// 十六進制與二進制:
val hex = 0xFF
val bin = 0b1010
}
// 無符號類型(1.5+):val u: UInt = 42u

數值運算

算術、位運算與數學函數。Int 除 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
// 初始化 lambda:
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 主構造需至少一個 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.引用與空安全

Kotlin 無裸指針:引用語義、可空類型、安全調用、Elvis、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。可空類型不能直接調用方法,必須先判空,這是 Kotlin 空安全基石。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
var name: String = "Kotlin" // 非空
var maybe: String? = null // 可空
// 不能直接調用:maybe.length 編譯錯誤
// 判空後再用:
if (maybe != null) {
println(maybe.length)
}
// 判空後編譯器智能轉換,可安全訪問
}
// 可空類型來自 Java 時可配合註解:
// @Nullable -> 可空,@NotNull -> 非空
// Java 無註解的類型是平台類型(String!)

安全調用

?. 在對象非空時調用方法,為空則整個表達式為 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 運算符

?: 左側為空時返回右側默認值。右側可為表達式,也可提前 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() ?: ""
}
// 右側可以是提前返回:
fun read(input: String?): String {
return input ?: return "空"
}
// 或拋異常:
// val value = maybe ?: throw IllegalArgumentException()
// Elvis 左側必須可空,右側非空

非空斷言

!! 把可空類型強轉為非空,為空時拋 NPE。應避免使用,僅在邏輯上確定非空時用。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val maybe: String? = "hello"
val len = maybe!!.length // 強制非空
println(len)
// 為 null 時拋 NullPointerException
}
// 更好的替代:
// 1. 安全調用 + Elvis:
// val l = maybe?.length ?: 0
// 2. 提前校驗明確錯誤:
// val v = maybe ?: error("配置缺失")
// 3. require 前置校驗
// 團隊評審應攔截濫用 !!

lateinit 屬性

lateinit 聲明稍後初始化的非空 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,非空類型
// 不能是基本類型(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+)
// 僅一個屬性
// 編譯期內聯,運行時無包裝對象
// 適合包裝類型: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 先執行後判斷。條件必須是布爾表達式。

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 至少執行一次:
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

智能轉換

類型檢查後編譯器自動轉換類型,無需顯式強轉。判空、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 僅在必要時使用

標籤與跳轉

標籤 @ 標記循環或 lambda,配合 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 標籤返回:
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 相反。常用於鏈式判空與過濾。

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.函數與 Lambda

函數聲明、lambda、高階函數、擴展函數、作用域函數與中綴函數。

函數聲明

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)
// 通常放在參數列表末尾

Lambda 表達式

Lambda 是匿名函數,花括號包裹,-> 分隔參數與函數體。最後參數是 Lambda 時可用尾隨語法。

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
// 單參數省略聲明,用 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
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)
// 格式字符串錯誤不報編譯錯,運行期拋異常

字符串轉換

字符串與數字互轉、轉布爾。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 在調用處展開函數體,消除 Lambda 對象創建開銷。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:循環內 + 拼接反覆創建字符串

基本類型裝箱

非空基本類型局部變量用原始類型,可空或泛型時裝箱。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:圖形界面看堆與 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")
}
// 主構造至少一個 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
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 靜態。可用 @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 { }
// 一個類只能有一個伴生對象

泛型

泛型參數化類型。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 流式逐行:
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) }
// 惰性逐行(大文件):
File("big.log").useLines { seq ->
seq.filter { "ERROR" in it }
.take(5)
.forEach { println(it) }
}
}
// useLines 自動關閉文件
// 大文件不要用 readLines 一次讀入
// forEachLine 便捷逐行遍歷

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 讀取一行,readlnOrNull 讀取可空。讀後手動轉換為所需類型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
println("輸入名字:")
val name = readln() // 讀一行
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 成對展示。

濫用 !!

!! 在空值時拋 NPE,應避免。用安全調用、Elvis 或提前校驗替代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val maybe: String? = null
// BAD:空值直接崩潰
// 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)
}
// !! 只在邏輯上保證非空時使用
// 團隊評審應攔截濫用 !!

智能轉換失效

可變屬性或委託屬性智能轉換失敗。賦值後需重新檢查或使用局部變量。

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"。需顯式處理空值。

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 ?: "未知"}"
// 模板裏屬性訪問記得加花括號

模板可空陷阱

模板 $a.b 解析為 a.b 屬性。可空對象屬性訪問需 ${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 更安全
// 或可空類型 + 判空

作用域函數混淆

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
// 需判空 -> let + ?.

伴生對象混淆

伴生對象成員不是類靜態字段。Kotlin 用伴生對象實現靜態語義,需 @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 {
// 方式一:原子類
val jobs = List(10) {
launch { repeat(1000) { counter.incrementAndGet() } }
}
jobs.forEach { it.join() }
println(counter.get()) // 10000
// 方式二:協程互斥鎖
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 Socket、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+ 內置 HttpClient 發送 HTTP 請求。支持異步 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 Socket

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 服務器
// 適合本地工具與簡單接口
// 生產推薦 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 與操作系統屬性。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 到 \\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, "#"))
// 首次替換:
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% 的常見用法。內容偏向現代慣用法:空安全(可空類型 ?、安全調用 ?.、Elvis ?:)、data class 與 sealed class、作用域函數、擴展函數,以及協程與 Flow 的異步併發模型。權威參考見官方 Kotlin 文檔與 Kotlin in Action。 19 個章節各自聚焦一個主題——從第一個程序到協程、空安全與常見誤區。每節拆成 8–14 個帶示例的小節(每個 5–20 行),共約 160 個主題。代碼片段刻意短小、自解釋,註釋為簡體中文。 所有處理都在瀏覽器中完成——無上傳、無追蹤。本頁是 GuruToolkit 免費開發者工具集的一部分;代碼片段可自由使用,無任何擔保。

版本 2.1.0