型別系統入門
前言
為什麼 "1" + 1 在 JavaScript 裡得到 "11",在 Python 裡卻直接報錯? 這背後就是型別系統在起作用。型別系統是程式語言的「交通規則」——它決定了資料能怎麼用、能和誰運算、什麼時候檢查合不合法。理解型別系統,你就能理解不同語言的「性格差異」。
這篇文章會帶你學什麼?
學完這章後,你將獲得:
- 分類能力:掌握靜態/動態、強/弱型別的四象限分類法
- 問題診斷:看到
TypeError時能快速定位是型別不匹配還是隱式轉換 - 語言選擇:理解為什麼 TypeScript 適合大型專案、Python 適合快速原型
- 型別推斷:理解現代語言如何兼顧簡潔和安全
- 實踐意識:掌握型別安全的編碼習慣
| 章节 | 內容 | 核心概念 |
|---|---|---|
| 第 1 章 | 什麼是型別系統 | 型別的本質、為什麼需要型別 |
| 第 2 章 | 靜態型別 vs 動態型別 | 檢查時機、IDE 支援、安全性 |
| 第 3 章 | 強型別 vs 弱型別 | 隱式轉換、型別安全 |
| 第 4 章 | 型別推斷 | 自動推斷、兩全其美 |
| 第 5 章 | 泛型:寫一次,適用所有型別 | 型別參數、型別約束、複用 |
| 第 6 章 | 型別安全實戰 | 常見陷阱、防禦策略 |
| 第 7 章 | 語言型別象限圖 | 四象限分類、語言選擇 |
0. 全景圖:型別是資料的「身分證**
型別系統就是程式語言用來管理這些「身分」的規則體系。它回答兩個核心問題:
型別系統的兩個核心問題
- 何時檢查? 是寫程式碼時就檢查(靜態型別),還是執行時才檢查(動態型別)?
- 多嚴格? 是嚴格禁止混用(強型別),還是自動幫你轉換(弱型別)?
1. 什麼是型別系統:資料的交通規則
Type System ExplorerStatic vs dynamic · strong vs weak typing · type inference
StrongWeakStaticDynamic
Strong + static
JavaRustHaskell
Weak + static
CC++
Strong + dynamic
PythonRuby
Weak + dynamic
JavaScriptPHP
Strong + static
Strict compile-time checking with no implicit conversion. Very safe and IDE-friendly, but more verbose.
Compile-time checksNo implicit conversionAutocomplete-friendlySafe refactoring
Core idea:Type systems choose along two dimensions: when checks happen (static/dynamic) and whether implicit conversion is allowed (strong/weak). There is no best combination, only the best fit for a scenario.
| 型別系統的作用 | 說明 | 例子 |
|---|---|---|
| 防止非法運算 | 阻止無意義的操作 | 不能對字串做除法 |
| 提供文件資訊 | 型別就是最好的文件 | function add(a: number, b: number) 一目了然 |
| 輔助 IDE 工具 | 自動補全、重構、跳轉 | 輸入 user. 自動提示所有屬性 |
| 最佳化效能 | 編譯器知道型別後能產生更快的程式碼 | 知道是整數就用整數指令 |
2. 靜態型別 vs 動態型別:什麼時候檢查?
🔍 Static vs Dynamic Typing: Live Comparison
Choose a code sample and compare how the two type systems behave
Static typing (TypeScript)⏱ Checked at compile time
let name: string = "Alice" name = 42 // ❌ compile error
❌ Type "number" is not assignable to type "string"
VS
Dynamic typing (JavaScript)⏱ Checked at runtime
let name = "Alice" name = 42 // ✅ OK
✅ Runs normally; name becomes 42
💡 Static typing catches the error while you write code. Dynamic typing waits until runtime.
核心區別
- 靜態型別:變數的型別在編譯時就確定了。代表:Java、TypeScript、Rust、Go。
- 動態型別:變數的型別在執行時才確定。代表:Python、JavaScript、Ruby、PHP。
| 維度 | 靜態型別 | 動態型別 |
|---|---|---|
| 檢查時機 | 編譯時 | 執行時 |
| 發現 bug | 早 | 晚 |
| 靈活性 | 較低 | 較高 |
| IDE 支援 | 好 | 較弱 |
趨勢:動態語言在「靜態化」
Python 加了 Type Hints,JavaScript 社群轉向 TypeScript——動態語言也在擁抱靜態型別的好處。
3. 強型別 vs 弱型別:允不允許「偷偷轉換」?
⚡ Strong vs Weak Typing: Implicit Conversion Lab
Choose an expression and see how different languages handle it
JavaScriptWeak
"1" + 1
→ "11" (string concatenation)
PythonStrong
"1" + 1
→ TypeError: can only concatenate str to str
JavaWeak
"1" + 1
→ "11" (string concatenation)
RustStrong
"1" + 1
→ compile error: type mismatch
📌 Strongly typed languages refuse to guess your intent. Weakly typed languages may helpfully convert, but the result may be wrong.
核心區別
- 強型別:不允許隱式型別轉換,型別不匹配就報錯。
- 弱型別:允許隱式型別轉換,語言會「好心」幫你自動轉。
| 維度 | 強型別 | 弱型別 |
|---|---|---|
"1" + 1 | 報錯或需明確轉換 | 自動轉換 |
| 安全性 | 高 | 低 |
| 可預測性 | 高 | 低 |
4. 型別推斷:兩全其美的現代方案
🧠 Type Inference: How the Compiler Guesses Types
Click a code line to see how the compiler infers the type step by step
1let x = 42 → number
2let names = ["Alice", "Bob"]
3let result = x > 10 ? "big" : "small"
4const add = (a: number, b: number) => a + b
5let mixed = [1, "two", true]
Inference process
1The right side is literal 42
242 is an integer-like number
3Infer x as number
Type Inference Capability by Language
RustAlmost fully inferred
TypeScriptMost types inferred
KotlinStrong local inference
GoMainly := short declarations
Javavar keyword (Java 10+)
CAlmost none
型別推斷的價值
寫著像動態語言一樣簡潔,編譯器檢查像靜態語言一樣嚴格。
- TypeScript:
let x = 42自動推斷為number - Rust:
let v = vec![1, 2, 3]自動推斷為Vec<i32> - Go:
x := 42短變數宣告自動推斷型別
5. 泛型:寫一次,適用所有型別
🧩 Generics: Write Once, Use with Any Type
Choose a scenario and see how generics keep code flexible and safe
❌ Without generics
// Need one function per type
function getFirstNumber(arr: number[]): number {
return arr[0]
}
function getFirstString(arr: string[]): string {
return arr[0]
}
// boolean, object... it never endsYou repeat the same code for every type.
✅ With generics
// One generic function handles all types
function getFirst<T>(arr: T[]): T {
return arr[0]
}
getFirst<number>([1, 2, 3]) // → number
getFirst<string>(["a", "b"]) // → stringT is a type parameter and is replaced by the actual type at call time.
Type flow
T = number→arr: number[]→return: number| 泛型特性 | 說明 | 範例 |
|---|---|---|
| 泛型函式 | 函式的參數/返回值使用型別參數 | function first<T>(arr: T[]): T |
| 泛型類別 | 類別的屬性/方法使用型別參數 | class Box<T> { value: T } |
| 泛型約束 | 用 extends 限制 T 的範圍 | <T extends HasLength> |
6. 型別安全實戰:常見陷阱與防禦
🛡️ Type Safety in Practice: Traps and Defenses
Choose a common trap and learn how the type system protects code
⚠️ Dangerous code
function getLength(str) {
return str.length // what if str is null?
}
getLength(null) // 💥 runtime crash💥 TypeError: Cannot read properties of null
✅ Safe code
function getLength(str: string | null): number {
if (str === null) return 0
return str.length // ✅ compiler knows str is not null here
}✅ The compiler forces you to handle null
🔑 Defense strategy
- Enable strictNullChecks
- Use string | null to mark nullable values explicitly
- Use optional chaining ?. for safe access
型別安全的四條黃金法則
- 開啟嚴格模式:TypeScript 的
strict: true - 避免 any:用
unknown代替any - 明確處理 null:用可選鏈
?.和空值合併?? - 為 API 定義介面:外部資料永遠不可信
| 陷阱 | 危險程度 | 防禦手段 |
|---|---|---|
| null/undefined 參照 | 極高 | strictNullChecks + 可選鏈 |
| any 型別濫用 | 高 | 用 unknown + 型別守衛 |
| 隱式型別轉換 | 中 | 嚴格比較 === + ESLint |
7. 語言型別象限圖
Programming Language Type ModelsHow type systems differ across languages
When types are checked
Type strength
Type System Classification Matrix
Static + strong
Java, C++, Rust, Go
Compile-time checks with type safety
Static + weak
C
Compile-time checks with flexible conversion
Dynamic + strong
Python, Ruby
Runtime checks with type safety
Dynamic + weak
JavaScript, PHP
Runtime checks with flexible typing
Type Inference
Modern languages can infer variable types automatically without explicit declarations.
TypeScript
let x = 5; // inferred as number
let name = "Alice"; // stringRust
let x = 5; // inferred as i32
let name = "Alice"; // &str| 象限 | 特點 | 代表語言 | 適用場景 |
|---|---|---|---|
| 靜態 + 強型別 | 最安全 | Rust, Java, Haskell | 大型系統 |
| 靜態 + 弱型別 | 編譯時檢查但允許隱式轉換 | C, C++ | 系統程式設計 |
| 動態 + 強型別 | 執行時檢查,不允許隱式轉換 | Python, Ruby | 腳本、快速原型 |
| 動態 + 弱型別 | 最靈活,也最容易出 bug | JavaScript, PHP | Web 前端、小腳本 |
沒有「最好」的型別系統
- 快速原型:動態型別(Python)開發速度快
- 大型專案:靜態型別(TypeScript、Java)維護成本低
- 系統程式設計:強型別 + 靜態(Rust)安全性最高
總結
- 型別是身分證:每種資料都有型別,型別決定了資料能參與什麼運算
- 靜態 vs 動態:何時檢查型別——編譯時還是執行時
- 強 vs 弱:是否允許隱式型別轉換
- 型別推斷:現代語言讓你享受動態的簡潔和靜態的安全
- 泛型:用型別參數實作程式碼複用
- 四象限分類:沒有最好的型別系統,只有最適合場景的選擇