編碼 · 程式語言
Kotlin 入門教學
簡答
Kotlin 是 JetBrains 推出的現代靜態型別語言,執行於 JVM 上,也是 Android 開發的首選語言。它語法精簡、與 Java 完全互通,其型別系統能在編譯期避免大多數空指標錯誤。本頁以可執行的範例介紹環境設定、變數、空值安全、流程控制、函式、集合、類別、協程與錯誤處理。
Kotlin 是什麼、可以拿來做什麼
Kotlin 是由 JetBrains 開發的靜態型別通用程式語言。它執行於 Java 虛擬機器(JVM),也可以編譯成 JavaScript、WebAssembly 與原生機器碼。Kotlin 與 Java 完全互通:Kotlin 檔案可以呼叫任何 Java 函式庫,Java 程式碼也能呼叫 Kotlin。
Google 自 2019 年起將 Kotlin 列為 Android 應用程式的首選語言,因此多數新的 Android 程式碼都是用 Kotlin 寫的。在伺服器端,它也常搭配 Spring Boot、Ktor 等框架使用;Kotlin Multiplatform 則讓團隊能在 Android、iOS、桌面與網頁之間共用商業邏輯。
安裝 Kotlin 並執行 Hello, world
最簡單的方式是使用內建 Kotlin 的 IntelliJ IDEA 或 Android Studio。若要在命令列使用,請安裝編譯器(例如透過 SDKMAN! 或 Homebrew)以及 Java 執行環境(JDK)。Kotlin 程式從頂層的 main 函式開始執行,不需要任何類別。
// hello.kt
fun main() {
println("Hello, world!")
}kotlinc hello.kt -include-runtime -d hello.jar
java -jar hello.jar
# Hello, world!實際專案通常會使用 Gradle(搭配 Kotlin DSL)等建置工具。你也可以直接在線上的 Kotlin Playground 試寫程式,不必安裝任何東西。
變數與基本型別
用 val 宣告唯讀變數,用 var 宣告可變變數。編譯器會自動推斷型別,你也可以在冒號後面明確寫出。建議優先使用 val,程式會更容易理解。
val name = "Ada" // String, cannot be reassigned
var count: Int = 0 // explicit type, can change
count += 1
val pi = 3.14159 // Double
val big = 9_000_000_000L // Long
println("$name has $count item(s), pi is ${"%.2f".format(pi)}")
// Ada has 1 item(s), pi is 3.14| 型別 | 範例 | 說明 |
|---|---|---|
Int | 42 | 32 位元有號整數 |
Long | 42L | 64 位元有號整數 |
Double | 3.14 | 64 位元浮點數 |
Boolean | true | true 或 false |
Char | 'k' | 單一字元 |
String | "text" | 不可變;支援 $name 字串模板 |
Kotlin 不會隱含地擴大數值型別:Int 不會自動轉成 Long。請明確呼叫 toLong()、toDouble() 或 toString()。
空值安全
空值安全是 Kotlin 最具代表性的特色。一般型別(例如 String)永遠不會是 null。若要允許 null,就在型別後加上 ?,例如 String?,編譯器便會強制你在使用該值之前先處理 null 的情況。
fun greet(name: String?) {
// println(name.length) // compile error: name may be null
println(name?.length) // safe call: prints null if name is null
val len = name?.length ?: 0 // Elvis operator: default value
if (name != null) {
println(name.uppercase()) // smart cast: name is String here
}
println("length = $len")
}
fun main() {
greet("kotlin") // 6, KOTLIN, length = 6
greet(null) // null, length = 0
}流程控制:if、when 與迴圈
在 Kotlin 中,if 與 when 都是運算式,因此會回傳值。when 取代了 Java 與 C 的 switch 陳述式,可以比對值、範圍與型別。
val score = 82
val grade = if (score >= 50) "pass" else "fail"
val letter = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
else -> "F"
}
println("$grade $letter") // pass B
for (i in 1..3) print("$i ") // 1 2 3
println()
for (i in 10 downTo 0 step 5) print("$i ") // 10 5 0
println()
var n = 3
while (n > 0) { print(n); n-- } // 321
println()1..3 是包含頭尾的閉區間;0 until 3(或 0..<3)則不含結尾,用來處理索引時通常比較合適。
函式與 Lambda
函式以 fun 宣告。參數可以有預設值,呼叫時可使用具名引數,函式本體只有一個運算式時可以用 = 簡寫。
fun area(width: Double, height: Double = 1.0): Double = width * height
fun main() {
println(area(3.0, 2.0)) // 6.0
println(area(width = 4.0)) // 4.0 (default height)
val square = { x: Int -> x * x } // a lambda stored in a variable
println(square(5)) // 25
val nums = listOf(1, 2, 3, 4, 5)
val evensSquared = nums.filter { it % 2 == 0 }.map { it * it }
println(evensSquared) // [4, 16]
}當 Lambda 只有一個參數時,可以用 it 來指稱它。若函式的最後一個引數是 Lambda,可以寫在括號外面,所以 filter { ... } 讀起來就像內建的語法區塊。
集合與資料類別
標準函式庫把唯讀與可變集合分開:listOf、setOf、mapOf 建立唯讀集合,而 mutableListOf、mutableSetOf、mutableMapOf 建立可修改的集合。
val fruits = mutableListOf("apple", "banana")
fruits.add("cherry")
val ages = mapOf("Ann" to 31, "Bo" to 25)
println(fruits.size) // 3
println(ages["Ann"]) // 31
println(ages["Zed"]) // null (map lookups are nullable)
data class User(val name: String, val age: Int)
val u = User("Ann", 31)
val older = u.copy(age = 32)
println(u) // User(name=Ann, age=31)
println(u == User("Ann", 31)) // true (structural equality)
val (n, a) = older // destructuring
println("$n $a") // Ann 32data class(資料類別)會自動取得 equals、hashCode、toString、copy 與解構功能。Kotlin 中的 == 比較的是內容(會呼叫 equals),=== 則比較參照。
類別、擴充函式與協程
類別預設是 final,要允許繼承必須加上 open。擴充函式可以在不修改既有型別的情況下為它新增方法,sealed 類別則用來表示封閉的情況集合,讓 when 能做窮盡檢查。
fun String.shout(): String = uppercase() + "!"
sealed class Shape
data class Circle(val r: Double) : Shape()
data class Rect(val w: Double, val h: Double) : Shape()
fun area(s: Shape): Double = when (s) {
is Circle -> Math.PI * s.r * s.r
is Rect -> s.w * s.h
} // no else needed: the compiler knows all cases
fun main() {
println("hi".shout()) // HI!
println(area(Rect(2.0, 3.0))) // 6.0
}在非同步與並行程式碼方面,Kotlin 使用 kotlinx.coroutines 函式庫提供的協程(coroutine)。suspend 函式可以暫停而不會阻塞執行緒,launch、async 等建構器則在作用域內啟動協程。Android 應用程式就是靠這個機制在主執行緒之外進行網路請求。
錯誤處理
Kotlin 和 Java 一樣有 try、catch 與 finally,但所有例外都是非受檢例外:你不必宣告或強制捕捉它們。try 同樣是運算式。對於可預期的失敗,標準函式庫提供 runCatching 與 Result,toIntOrNull() 這類函式也會回傳 null 而不是拋出例外。
fun parse(s: String): Int = try {
s.toInt()
} catch (e: NumberFormatException) {
-1
}
fun main() {
println(parse("42")) // 42
println(parse("x")) // -1
println("7".toIntOrNull()) // 7
println("seven".toIntOrNull()) // null
val r = runCatching { "abc".toInt() }
println(r.isFailure) // true
}完整的小程式:詞頻統計
這個程式會統計文字中每個單字出現的次數,並印出前三名。它用到字串擴充、集合函式與資料類別。
data class WordCount(val word: String, val count: Int)
fun topWords(text: String, n: Int): List<WordCount> =
text.lowercase()
.split(Regex("[^a-z']+"))
.filter { it.isNotBlank() }
.groupingBy { it }
.eachCount()
.map { (w, c) -> WordCount(w, c) }
.sortedWith(compareByDescending<WordCount> { it.count }.thenBy { it.word })
.take(n)
fun main() {
val text = "The cat and the dog. The dog sat, and the cat ran."
for ((word, count) in topWords(text, 3)) {
println("$word: $count")
}
}
// the: 4
// and: 2
// cat: 2接下來可以學什麼
- Kotlin 官方文件(kotlinlang.org),建議從 Basic syntax 與 Kotlin tour 開始。
- Kotlin Koans:可直接在瀏覽器中執行的小型練習題。
- Android Developers 的 Kotlin 開發指南,或 Ktor 與 Spring 的伺服器端 Kotlin 指南。
- 需要撰寫非同步程式碼時,閱讀 kotlinx.coroutines 指南。
常見問題
Kotlin 比 Java 好嗎?
Kotlin 更精簡,並加入了空值安全、資料類別與協程,但它與 Java 執行在同一個 JVM 上、使用相同的函式庫。許多團隊會在同一個專案中混用兩種語言,所以選擇取決於風格與開發效率,而非能力差異。
學 Kotlin 之前需要先學 Java 嗎?
不需要。你可以把 Kotlin 當作第一個 JVM 語言來學。懂一點 Java 對閱讀舊的 Android 程式碼或 Java 函式庫文件會有幫助。
val 和 var 有什麼差別?
val 宣告初始化後不能重新指派的唯讀參照,var 則宣告可以重新指派的參照。指向可變清單的 val,其內容仍然可以被修改。
Kotlin 可以開發 iOS 應用程式嗎?
可以,透過 Kotlin Multiplatform,把共用的 Kotlin 程式碼編譯成 iOS 的原生程式碼。使用者介面通常仍以 Swift 撰寫,不過 Compose Multiplatform 也能共用 UI 程式碼。
Kotlin 的 ?: 運算子有什麼作用?
它是 Elvis 運算子:a ?: b 在 a 不為 null 時回傳 a,否則回傳 b。常與安全呼叫搭配,例如 name?.length ?: 0。