# Kotlin / Ch01 - Kotlin Fundamental
## 第一章:Kotlin 簡介
### 1.1 什麼是 Kotlin
Kotlin 是一種現代化的程式語言,由 JetBrains 公司開發,名稱來源於俄羅斯的柯特林島 (Kotlin Island)。作為 JVM (Java Virtual Machine) 上的語言,Kotlin 與 Java 完全互通,同時提供了更簡潔和安全的程式設計體驗。
### 1.2 Kotlin 的發展背景
在程式語言的生態系統中,Java 由 Sun Microsystems (後被 Oracle 收購) 開發,主要使用 Eclipse IDE 進行開發。而 Kotlin 則是由 JetBrains 公司創造,搭配 IntelliJ IDEA 作為主要的開發環境。兩種語言都運行在 JVM 上,最終編譯成相同的 Binary Code,這意味著它們可以無縫互通。
```mermaid
graph TD
A[Kotlin] --> D[JVM]
B[Java] --> D[JVM]
D --> E[Binary Code]
```
### 1.3 為什麼選擇 Kotlin
#### 1.3.1 官方支援地位
Google 於 2017 年宣布 Kotlin 為 Android 應用程式開發的官方語言,這標誌著 Kotlin 在行動應用開發領域的重要地位。開發者可以使用 Kotlin 來建構完整的 Android 應用程式,享受與 Java 相同的功能但具備更佳的語言特性。
#### 1.3.2 後端開發的優勢
在後端開發方面,Spring Framework 對 Kotlin 提供了一流的支援。Spring 框架讓開發者能夠撰寫 Kotlin 應用程式,幾乎就像 Spring Framework 本身就是原生的 Kotlin 框架一樣。官方文件中的大部分程式碼範例都同時提供 Kotlin 和 Java 版本,顯示了對 Kotlin 的重視程度。
#### 1.3.3 與其他框架的比較
相比於傳統的 Spring 框架,Kotlin 也支援其他輕量級框架如 Ktor。Spring 是一個成熟的框架系列,擁有完整的生態系統,被全世界數百萬開發者使用。而 Ktor 則是為重視架構設計自由度的開發者提供的輕量級選擇,讓他們能夠建立 HTTP API、WebSocket 聊天應用,以及互動式網站。
## 第二章:基本語法與資料型別
### 2.1 資料型別系統
Kotlin 的型別系統與 Java 有顯著差異。在 Kotlin 中,所有的型別都是物件,沒有 Java 中的基礎型別 (primitive types) 如 `int`、`float` 等。
#### 2.1.1 數值型別
Kotlin 提供以下數值型別,按照大小排序:
- **Long**:64 位元整數
- **Int**:32 位元整數
- **Short**:16 位元整數
- **Byte**:8 位元整數
- **Double**:64 位元浮點數
- **Float**:32 位元浮點數
#### 2.1.2 其他基本型別
- **Char**:字元型別
- **Boolean**:布林值 (true/false)
### 2.2 變數宣告
Kotlin 使用關鍵字來宣告變數,並支援型別推斷,這意味著編譯器能夠自動判斷變數的型別。語法結構不需要使用分號 (`;`) 結尾。
#### 2.2.1 變數宣告關鍵字
| 關鍵字 | 特性 | 等同於 Java | 範例 |
|--------|------|-------------|------|
| `var` | 可變變數 | 一般變數宣告 | `var a = 100` |
| `val` | 不可變變數 | `final` 修飾符 | `val b = 20` |
| `const val` | 編譯時期常數 | `static final` | 只能在全域範圍定義 |
#### 2.2.2 實際範例
```kotlin=
fun main() {
var a = 100
a = 150
// a = "Boy" // 錯誤:var 推導後型別不可變
println("a = $a")
val b = 20
// b = 25 // 錯誤:val 描述後不可再賦值
println("b = $b")
var d: Short = 1 // 明確指定型別
d = 11
println("d = $d")
// String Interpolation (字串插值)
var e = 174
var c: String = "$e ${e + 33} Interpolation"
println(c)
}
```
### 2.3 函數基礎
#### 2.3.1 函數語法結構
在 Kotlin 中,函數使用 `fun` 關鍵字定義。基本語法如下:
```kotlin=
fun functionName(parameter1: Type, parameter2: Type): ReturnType {
// 函數主體
return result
}
```
重要特點:
- `fun` 用於宣告函數
- `main()` 函數是程式的進入點
- 函數主體使用大括弧 `{}` 包圍
- 參數寫在圓括弧內,每個參數必須指定型別
- 回傳型別寫在函數括弧後,用冒號 `:` 分隔
- 如果函數不回傳有用的值,回傳型別和 `return` 關鍵字可以省略
#### 2.3.2 字串插值 (String Interpolation)
Kotlin 提供強大的字串插值功能,讓開發者能夠輕鬆地在字串中嵌入變數值,~~相當有 JavaScript 的既視感~~:
```kotlin=
val name = "Kotlin"
val version = 1.8
val message = "Hello, $name! Version: $version"
val calculation = "計算結果:${10 + 20}"
```
## 第三章:條件控制與流程
### 3.1 if/else 條件判斷
Kotlin 中的 `if` 語句可以作為表達式使用,這意味著它可以回傳值而不需要明確使用 `return` 關鍵字。
```kotlin=
fun max(a: Int, b: Int): Int {
// if 作為表達式,直接回傳最大值
val result = if (a > b) a else b
return result
}
fun main() {
val x = 10
val y = 20
println("The max value is: ${max(x, y)}")
}
```
### 3.2 when 語句
`when` 是 Kotlin 用來取代 Java 中 `switch-case` 語句的現代化替代方案,提供更強大和靈活的功能。
```kotlin=
fun useWhenSimple(id: Int) = when(id) {
1 -> "A"
2 -> { // 多行時使用大括弧
"B"
}
3 -> "C"
else -> "Non" // 類似 default
}
// 型別檢查 (類似 Java 的 instanceof)
fun useIs(id: Number): Unit = when(id) {
is Int -> println("參數是 Int 型別")
is Double -> println("參數是 Double 型別")
is Float -> println("參數是 Float 型別")
else -> println("無法判斷型別")
}
```
範例中用到的 `->` 符號稱為 Lambda 表達式,先不用太在意有個印象就好,等到第四章我們再來詳細聊它。
### 3.3 相等性比較
Kotlin 提供兩種相等性比較方式,與 Java 的概念不同:
| 功能 | Java | Kotlin |
|------|------|--------|
| 內容相等 | `.equals()` | `==` |
| 參考相等 | `==` | `===` |
```kotlin=
var a: String = "Hello"
var b: String = String(StringBuffer("Hello"))
println("a.equals(b): ${a.equals(b)}") // true
println("a == b: ${a == b}") // true
println("a === b: ${a === b}") // false
```
這種設計讓程式碼更加直觀,因為大多數情況下開發者關心的是內容是否相等,而非物件參考是否相同。另外,關於 StringBuffer 的介紹可以參考:[Kotlin / Ch02 - 並行程式設計 第三章](https://hackmd.io/Pik-HRodS0eRQmUOYGcq5g?view#%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9AMutable-%EF%BC%86-Immutable)
## 第四章:Lambda 表達式與高階函數
### 4.1 Lambda 表達式基礎
Lambda 表達式,又稱匿名函數 (Anonymous Function),是 Kotlin 函數式程式設計的重要概念。Lambda 表達式讓開發者能夠將函數當作值來傳遞和操作。
#### 4.1.1 Lambda 語法結構
Lambda 表達式的基本語法:
```kotlin
val lambdaName: Type = { parameter: Type -> body }
```
Lambda 表達式包含兩個主要部分:
**型別部分 (Type Part)**
- `(Int, Int) -> Unit`:代表兩個 Int 參數,無回傳值
- `(Int, Int) -> Int`:代表兩個 Int 參數,回傳 Int 型別
**物件部分 (Object Part)**
- 參數清單在 `->` 前面
- 函數主體在 `->` 後面
- 最後一行會作為回傳值
#### 4.1.2 實際範例
```kotlin=
val square: (Int) -> Int = { x -> x * x }
println(square(5)) // 輸出 25
val printInfo: (String) -> Unit = { name ->
println("名字: $name")
println("字數: ${name.length}")
}
printInfo("Kotlin")
```
### 4.2 高階函數 (Higher-Order Function)
高階函數是指接收其他函數作為參數,或者回傳函數的函數。這是函數式程式設計的核心概念。
#### 4.2.1 函數作為參數
```kotlin=
fun passMeFunction(abc: () -> Unit) {
abc() // 或者使用 abc.invoke()
}
fun main() {
passMeFunction { println("Hello !") }
}
```
#### 4.2.2 函數作為回傳值
```kotlin=
fun returnMeAddFunction(): ((Int, Int) -> Int) {
return { a: Int, b: Int -> a + b }
}
fun main() {
val add = returnMeAddFunction()
val result = add(2, 2)
println(result) // 輸出 4
}
```
### 4.3 Lambda 表達式的語法簡化
#### 4.3.1 使用 it 參數
當 Lambda 表達式只有一個參數時,可以使用 `it` 來代替明確的參數名稱:
```kotlin
val selectedPerson = people.maxBy({ person -> person.age })
val selectedPerson = people.maxBy({ it.age }) // 簡化版本
```
#### 4.3.2 移除圓括弧
當 Lambda 表達式是函數的最後一個參數時,可以將其移到圓括弧外,當然這有時候會取決於公司的 Linter 規範:
```kotlin
passMeFunction({ println("Hello !") })
passMeFunction { println("Hello !") } // 簡化版本
```
### 4.4 Lambda 表達式常見用法
| 類型/功能 | 語法 | 範例 | 輸出 |
|----------|------|------|------|
| 基本語法 | `{ 參數 -> 主體 }` | `val sum = { a: Int, b: Int -> a + b }` | 8 |
| 單參數省略 | `{ it * it }` | `listOf(1,2,3).map { it * it }` | [1, 4, 9] |
| 無參數 | `{ -> 值 }` 或 `{ }` | `val hello: () -> String = { "Hi!" }` | Hi! |
| 忽略參數 | `{ x, _ -> x }` | `val f: (Int, Int) -> Int = { x, _ -> x }` | 5 |
| 傳入高階函數 | 函數(參數) `{ Lambda }` | `listOf(1,2,3).filter { it > 1 }` | [2, 3] |
| fold 聚合 | `{ acc, elem -> 處理 }` | `listOf("a","b","c").fold("") { acc, s -> acc + s }` | abc |
### 4.5 Lambda 與普通函數的比較
| 特性 | 普通函數 | Lambda |
|------|----------|--------|
| 是否有名字 | ✓ | ✗ 匿名 |
| 是否能當值傳遞 | 需要函數型別包裝 | ✓ 可直接當值 |
| 常見用途 | 一般邏輯 | 集合處理、回呼函式、DSL |
個人的小小心得,非常推薦在剛開始學習的時候,用力把每個表達式都看到真的懂,不要大概大概就好。久了之後看這些表達式的速度才會越來越快,熟了之後甚至我體感會覺得是在看整個程式的「形狀」,也就是腦袋肌肉記憶的一部份了。
## 第五章:集合物件 (Collections)
### 5.1 集合類型概述
Kotlin 提供三種主要的集合類型,每種都有其特定的用途和特性:
| 集合類型 | 描述 | 特性 |
|----------|------|------|
| Lists | 有序的項目集合 | 允許重複元素,保持插入順序 |
| Sets | 唯一無序的項目集合 | 不允許重複元素,無特定順序 |
| Maps | 鍵值對的集合 | 鍵必須唯一,可以有重複的值 |
### 5.2 可變與不可變集合
Kotlin 中的每種集合都有可變 (Mutable) 和不可變 (Immutable) 兩個版本。關於為什麼 Kotlin 要這樣設計,這牽涉到比較進階的內容(multi-threading & GC),所以在這邊就不多做贅述:
#### 5.2.1 List 範例
```kotlin=
// 不可變 List
val immutableList = listOf("Hung", "Jui", "Chung", "Hung")
println(immutableList.size) // 4
println(immutableList.indexOf("Jui")) // 1,找不到回傳 -1
println(immutableList[1]) // "Jui"
// 可變 List
val mutableList = mutableListOf("Hung", "Jui", "Chung")
mutableList.add("Android") // immutableList.add() 會出錯
println(mutableList)
```
#### 5.2.2 Set 範例
```kotlin
// Set 自動去除重複元素
val set = setOf("Hung", "Jui", "Chung", "Hung")
println(set) // [Hung, Jui, Chung] - "Hung" 只出現一次
```
#### 5.2.3 Map 範例
```kotlin=
val map = mutableMapOf(
"Hung" to 0,
"Jui" to 1,
"Chung" to 1,
"Mars" to 2,
"Android" to 79,
"Java" to 82,
"Kotlin" to 27
)
println(map.size) // 7
map["C++"] = 5 // 新增元素
map["Jui"] = 100 // 更新現有元素
println(map.size) // 8
println(map["Jui"]) // 100
```
### 5.3 集合操作與 Lambda
集合與 Lambda 表達式結合使用時,能夠實現強大的資料處理功能:
```kotlin=
val names = listOf("Hung", "Jui", "Chung")
// 使用 withIndex() 同時取得索引和值
for ((index, value) in names.withIndex()) {
println("$index: $value")
}
// 使用 Lambda 進行集合處理
names.filter { it.length > 3 } // 篩選長度大於3的名字
names.map { it.uppercase() } // 轉換為大寫
```
## 第六章:迴圈與區間操作
Kotlin 提供豐富的區間操作符來控制迭代行為:
| 操作符 | 說明 | 範例 |
|--------|------|------|
| `..` | 包含終點的區間 | `0..5` 包含 0,1,2,3,4,5 |
| `until` | 不包含終點的區間 | `0 until 5` 包含 0,1,2,3,4 |
| `step` | 指定步長 | `0 until 10 step 2` 輸出 0,2,4,6,8 |
| `downTo` | 降序區間 | `10 downTo 0 step 2` 輸出 10,8,6,4,2,0 |
| `in` | 判斷是否在區間內 | `if (x in 1..10)` |
```kotlin=
// 各種區間操作範例
for (i in 0..5) print("$i ") // 0 1 2 3 4 5
for (x in 0 until 5) println(x) // 0 1 2 3 4
for (x in 0 until 10 step 2) print("$x ") // 0 2 4 6 8
for (x in 10 downTo 0 step 2) println("$x ") // 10 8 6 4 2 0
```
## 第七章:函數進階概念
### 7.1 函數語法結構
完整的函數定義語法:
```kotlin
fun [函數名稱]([參數1名稱]: [參數1型別], [...其他參數]): [回傳型別] {
[函數內容]
}
```
### 7.2 函數簡化語法
#### 7.2.1 單行函數簡化
當函數只有一行時,可以使用等號 (`=`) 來簡化:
```kotlin=
// 傳統寫法
fun happyBirthday1(name: String, age: Int): String {
return "Happy ${age}th birthday, $name!"
}
// 簡化寫法
fun happyBirthday1(name: String, age: Int) = "Happy ${age}th birthday, $name!"
```
#### 7.2.2 Unit 回傳型別
當函數不需要回傳有意義的值時,可以省略 `Unit` 型別:
```kotlin=
fun happyBirthday2(name: String, age: Int): Unit =
println("Happy ${age}th birthday, $name!")
// 可以省略 Unit
fun happyBirthday2(name: String, age: Int) =
println("Happy ${age}th birthday, $name!")
```
### 7.3 高階函數應用
```kotlin=
fun operateTwoNumbers(a: Int, b: Int, f: (Int, Int) -> Int) = f(a, b)
fun main() {
val multiple = operateTwoNumbers(5, 15) { a, b -> a * b }
val add = operateTwoNumbers(5, 15) { a, b -> a + b }
println(multiple) // 75
println(add) // 20
}
```
這種設計模式讓函數變得極其靈活,能夠根據傳入的 Lambda 表達式執行不同的操作邏輯。
## 第八章:Null Safety (空值安全)
### 8.1 Java 與 Kotlin 的差異
傳統的 Java 程式設計中,`NullPointerException` 是最常見的執行時期錯誤之一。Java 在編譯階段並不會檢查空值問題,只有在程式執行時才會發現問題:
```java=
// Java 範例 - 編譯通過但執行時出錯
String name = null;
System.out.println(name.length()); // NullPointerException
```
### 8.2 Kotlin 的解決方案
Kotlin 透過型別系統在編譯時期就防止了空值相關的錯誤。在 Kotlin 中,只有在型別後加上問號 (`?`) 的變數才能接受空值:
```kotlin=
val message: String? = null // 可以為 null
val name: String = "Kotlin" // 不能為 null
val name: String? = null
// println(name.length()) // 編譯錯誤:無法直接呼叫可空型別的方法
```
### 8.3 處理可空型別
Kotlin 提供多種安全處理可空型別的方式:
#### 8.3.1 安全呼叫運算子 (?.)
```kotlin
val length = name?.length
// 如果 name 不為 null,回傳長度;否則回傳 null
```
#### 8.3.2 Elvis 運算子 (?:)
```kotlin
val length = name?.length ?: 0
// 如果結果為 null,使用預設值 0
```
#### 8.3.3 強制解包運算子 (!!)
```kotlin
val length = name!!.length
// 強制視為非空值,但如果為 null 會拋出異常
```
這種設計讓開發者在編寫程式時就必須考慮空值的情況,大大減少了執行時期因空值而產生的錯誤,提升了程式的穩定性和可靠性。