Kotlin 入门
语言哲学(Language Philosophy)
Kotlin 的设计不是凭空而来,而是基于三大核心哲学支柱。正如官方所说:"Kotlin is designed to be a pragmatic language that focuses on developer productivity."
简洁性原则(Conciseness Principle)
Kotlin 追求更少的样板代码(boilerplate code),让开发者专注于业务逻辑而非语法仪式。
// Java: 定义一个简单的数据类需要 50+ 行
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { /* ... */ }
}// Kotlin: 一行搞定,自动生成 equals/hashCode/toString/copy
data class User(val name: String, val age: Int)安全性设计(Safety by Design)
Kotlin 将安全性内置于类型系统,而非依赖外部检查或开发者自觉。
"The type system is designed to eliminate the danger of null references from code." — Kotlin 官方文档
// 空安全(Null Safety)是类型系统的一部分
var name: String = "Kotlin" // 非空类型,不能赋值 null
var nickname: String? = null // 可空类型(Nullable Type),明确允许 null
// 编译器强制你处理 null 情况
val length = nickname?.length // 安全调用(Safe Call),返回 Int?
val len = nickname?.length ?: 0 // Elvis 操作符,提供默认值其他安全特性:
-
智能类型转换(Smart Casts):类型检查后自动转换
-
不可变引用(Immutable References):
val声明后不可重新赋值 -
密封类(Sealed Classes):限制继承层次,
when表达式可穷尽检查
实用主义(Pragmatism)
Kotlin 不追求学术上的"纯粹",而是解决实际开发痛点。
┌─────────────────────────────────────────────────────┐
│ Kotlin 的实用主义体现 │
├─────────────────────────────────────────────────────┤
│ • 100% Java 互操作 → 渐进式迁移,不推倒重来 │
│ • 协程(Coroutines)→ 简化异步,不引入新线程模型 │
│ • 扩展函数 → 增强现有类,不修改源码 │
│ • 多平台(Multiplatform)→ 共享逻辑,不强求统一 UI │
└─────────────────────────────────────────────────────┘实用主义的一个典型例子是扩展函数(Extension Function):
// 为 String 类添加方法,无需继承或修改源码
fun String.addExclamation() = this + "!"
val greeting = "Hello".addExclamation() // "Hello!"📝 练习题
题目1: 以下哪项最能体现 Kotlin 的"简洁性原则"?
A. 所有变量必须显式声明类型
B. data class 自动生成常用方法
C. 禁止使用 null 值
D. 必须使用分号结束语句
【答案】B
【解析】data class 自动生成 equals()、hashCode()、toString()、copy() 等方法,大幅减少样板代码(boilerplate),是简洁性的典型体现。A 错误,Kotlin 支持类型推断;C 错误,Kotlin 允许可空类型;D 错误,Kotlin 分号是可选的。
题目2: Kotlin 的"实用主义"哲学意味着什么?
A. 追求类型系统的学术完美性
B. 优先解决实际开发痛点,支持渐进式采用
C. 完全抛弃 Java 生态,从零开始
D. 只支持函数式编程范式
【答案】B
【解析】Pragmatism 意味着 Kotlin 设计以解决实际问题为导向,比如与 Java 100% 互操作允许团队渐进式迁移(incremental adoption),而非强迫推倒重来。A 是学术主义;C 与事实相反;D 忽略了 Kotlin 的多范式特性。
Kotlin的历史与发展(History and Evolution)
JetBrains 的设计初衷(Design Motivation)
2010年,JetBrains 团队在开发 IntelliJ IDEA 时面临困境:
┌─────────────────────────────────────────────────────────────┐
│ JetBrains 当时的痛点 │
├─────────────────────────────────────────────────────────────┤
│ 1. 代码库庞大(millions of lines of Java code) │
│ 2. Java 语法冗长,开发效率受限 │
│ 3. 无法轻易切换到其他 JVM 语言(Scala 编译太慢) │
│ 4. 需要 100% 兼容现有 Java 代码和工具链 │
└─────────────────────────────────────────────────────────────┘
↓
创造一门新语言:Kotlin(以俄罗斯岛屿命名)JetBrains 的目标很明确:
"We wanted a language that would make us more productive while being fully compatible with our existing Java codebase."
Kotlin 的设计约束:
-
编译速度:不能比 Java 慢太多(对比 Scala)
-
互操作性:Java 调用 Kotlin、Kotlin 调用 Java 都要自然
-
工具支持:IDE 支持必须是一等公民(first-class citizen)
-
学习曲线:Java 开发者应该能快速上手
版本演进(Version Evolution)
时间线 Timeline
─────────────────────────────────────────────────────────────────►
2011 2016 2017 2019 2021 2024
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
首次公开 1.0 正式版 Google 官宣 1.3 协程 1.5 稳定 2.0 K2编译器
Project (Production Android 正式版 Multiplatform 新纪元
Kotlin Ready) 首选语言 Coroutines 进入 Beta2017 年是转折点:Google 在 I/O 大会上宣布 Kotlin 成为 Android 官方开发语言(official language for Android development),这让 Kotlin 从 JetBrains 的内部项目一跃成为主流语言。
📝 练习题
题目: JetBrains 设计 Kotlin 的首要动机是什么?
A. 创造一门纯函数式编程语言
B. 替代 JavaScript 进行前端开发
C. 在保持与 Java 兼容的前提下提高开发效率
D. 开发专用于移动端的编程语言
【答案】C
【解析】JetBrains 拥有大量 Java 代码库,需要一门能与 Java 100% 互操作(100% interoperable)、同时更简洁高效的语言。这是"实用主义"的体现——解决自己的实际问题。Kotlin 后来被用于 Android/iOS/Web/Server 等多平台,但这是演进结果而非最初目标。
基本概念(Fundamental Concepts)
表达式优先(Expression-Oriented)
Kotlin 是一门 表达式优先(expression-oriented) 的语言。这意味着大多数控制结构都是表达式(expression),会返回一个值。
"In Kotlin,
ifis an expression: it returns a value." — Kotlin 官方文档
// Java 风格:if 是语句(Statement),不返回值
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// Kotlin 风格:if 是表达式(Expression),返回值
val max = if (a > b) a else b // 类似三元运算符,但更强大语句与表达式的区别(Statement vs Expression)
┌─────────────────────────────────────────────────────────────┐
│ Statement vs Expression │
├──────────────────────────┬──────────────────────────────────┤
│ 语句 Statement │ 表达式 Expression │
├──────────────────────────┼──────────────────────────────────┤
│ 执行动作,不产生值 │ 求值并返回结果 │
│ 不能作为赋值的右侧 │ 可以赋值给变量 │
│ Java 的 if/when/try │ Kotlin 的 if/when/try │
└──────────────────────────┴──────────────────────────────────┘Kotlin 中的表达式示例:
// when 表达式(相当于增强版 switch)
val result = when (score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 60..79 -> "C"
else -> "F"
}
// try 表达式
val number = try {
input.toInt()
} catch (e: NumberFormatException) {
0 // 解析失败时返回默认值
}
// 代码块表达式:最后一行是返回值
val greeting = {
val hour = java.time.LocalTime.now().hour
if (hour < 12) "Good morning" else "Good afternoon"
}() // 立即执行重要区别: 赋值语句(assignment)在 Kotlin 中不是表达式!
// Java:赋值返回被赋的值(可能导致 = 和 == 混淆的 bug)
// while ((line = reader.readLine()) != null) { ... }
// Kotlin:赋值是语句,不返回值
var a = 1
var b = 2
// val c = (a = b) // ❌ 编译错误!赋值不是表达式不可变性思想(Immutability Philosophy)
Kotlin 鼓励使用不可变数据(immutable data),这是函数式编程(functional programming)的核心原则之一。
"Prefer
valovervar." — Kotlin 编码规范
// val = value(不可变引用,Immutable Reference)
val name = "Kotlin"
// name = "Java" // ❌ 编译错误:Val cannot be reassigned
// var = variable(可变引用,Mutable Reference)
var count = 0
count = 1 // ✅ 允许重新赋值不可变 vs 只读的区别(Immutable vs Read-only):
val list = mutableListOf(1, 2, 3) // list 引用不可变
list.add(4) // ✅ 但内容可以修改!
// 真正的不可变集合
val immutableList = listOf(1, 2, 3)
// immutableList.add(4) // ❌ 没有 add 方法┌───────────────────────────────────────────────────────────────┐
│ val vs var 图解 │
├───────────────────────────────────────────────────────────────┤
│ │
│ val name ──────────► "Kotlin" │
│ │ ▲ │
│ │ │ 引用不可变 │
│ └──────────────────┘ (Reference is immutable) │
│ │
│ var count ─────────► 0 │
│ │ │
│ └─────────────► 1 (Can be reassigned) │
│ │
│ val list ──────────► [1, 2, 3, 4] │
│ │ ▲ │
│ │ │ 引用不可变,但内容可变 │
│ └──────────────────┘ (Reference immutable, │
│ content mutable) │
└───────────────────────────────────────────────────────────────┘为什么推崇不可变性?
-
线程安全(Thread Safety):不可变对象天然线程安全
-
可预测性(Predictability):数据不会被意外修改
-
易于调试(Easier Debugging):状态变化更容易追踪
-
函数式编程基础:纯函数(Pure Functions)不修改外部状态
// 不可变风格:创建新对象而非修改
data class User(val name: String, val age: Int)
val user1 = User("Alice", 25)
val user2 = user1.copy(age = 26) // 创建新对象,原对象不变
println(user1) // User(name=Alice, age=25)
println(user2) // User(name=Alice, age=26)📝 练习题
题目1: 以下关于 Kotlin 中 if 的说法,正确的是:
A. if 只能作为语句使用,不能返回值
B. if 是表达式,可以返回值并赋给变量
C. if 表达式必须有 else 分支
D. if 作为表达式时必须用花括号包裹
【答案】B
【解析】Kotlin 中 if 是表达式(expression),会返回一个值。例如 val max = if (a > b) a else b。但注意:当 if 作为表达式(需要返回值)时,必须有 else 分支,否则编译器不知道另一种情况返回什么。选项 C 的表述不完整——只有用作表达式时才必须有 else。选项 D 错误,单表达式不需要花括号。
题目2: 以下代码的输出是什么?
val list = mutableListOf("A", "B")
list.add("C")
println(list.size)A. 编译错误,因为 val 不可变
B. 运行时错误
C. 2
D. 3
【答案】D
【解析】val 表示引用不可变(immutable reference),即 list 变量不能指向另一个列表。但 list 指向的是 MutableList,其内容是可变的。这就是"只读引用 vs 不可变对象"的区别。如果想要真正的不可变列表,应使用 listOf() 返回的 List<T>(没有 add 方法)。
与Java的关系(Relationship with Java)
Kotlin 从诞生之日起就被设计为与 Java 无缝协作。这不是事后补丁,而是核心设计目标。
"Kotlin is designed with Java interoperability in mind. Existing Java code can be called from Kotlin in a natural way, and Kotlin code can be used from Java rather smoothly as well." — Kotlin 官方文档
100%互操作性(100% Interoperability)
┌─────────────────────────────────────────────────────────────────┐
│ Kotlin ↔ Java 互操作 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Kotlin │ ──── 调用 ────► │ Java │ │
│ │ Code │ ◄─── 调用 ──── │ Code │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └───────────┬───────────────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ JVM Bytecode │ │
│ │ (.class 文件) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Kotlin 调用 Java:直接调用,无需任何包装
// Kotlin 代码直接使用 Java 类
import java.util.ArrayList
import java.time.LocalDateTime
fun main() {
// 调用 Java 集合
val list = ArrayList<String>()
list.add("Kotlin")
list.add("Java")
// 调用 Java 时间 API
val now = LocalDateTime.now()
println("Current time: $now")
// 调用 Java 静态方法
val maxValue = Integer.MAX_VALUE
}Java 调用 Kotlin:同样自然
// User.kt
class User(val name: String, val age: Int) {
fun greet() = "Hello, I'm $name"
companion object {
@JvmStatic // 让 Java 可以用 User.create() 调用
fun create(name: String) = User(name, 0)
}
}// Main.java
public class Main {
public static void main(String[] args) {
// Java 调用 Kotlin 类
User user = new User("Alice", 25);
System.out.println(user.getName()); // 自动生成 getter
System.out.println(user.greet());
// 调用伴生对象的静态方法
User newUser = User.create("Bob");
}
}// 默认参数 + @JvmOverloads = Java 可用的多个重载
class HttpClient {
@JvmOverloads
fun request(
url: String,
method: String = "GET",
timeout: Int = 5000
) { /* ... */ }
}
// Java 可以这样调用:
// client.request("http://...")
// client.request("http://...", "POST")
// client.request("http://...", "POST", 10000)迁移路径(Migration Path)
从 Java 迁移到 Kotlin 是一个渐进式过程(incremental process),不需要一次性重写。
┌─────────────────────────────────────────────────────────────────┐
│ Java → Kotlin 迁移策略 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: 新代码用 Kotlin │
│ ───────────────────────── │
│ • 新功能、新模块用 Kotlin 编写 │
│ • 现有 Java 代码保持不变 │
│ │
│ Phase 2: 逐步转换(Gradual Conversion) │
│ ───────────────────────────────────────── │
│ • IntelliJ 的 "Convert Java File to Kotlin" 功能 │
│ • 优先转换:工具类、数据类、独立模块 │
│ • 转换后 review + 优化(利用 Kotlin 特性重构) │
│ │
│ Phase 3: 深度 Kotlin 化 │
│ ─────────────────────── │
│ • 使用协程替换回调/RxJava │
│ • 使用扩展函数优化 API │
│ • 使用密封类替换枚举 + 状态模式 │
│ │
└─────────────────────────────────────────────────────────────────┘自动转换示例:
// Java 原始代码
public class StringUtils {
public static boolean isNullOrEmpty(String s) {
return s == null || s.isEmpty();
}
public static String capitalize(String s) {
if (isNullOrEmpty(s)) return s;
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
}// IntelliJ 自动转换结果(可进一步优化)
object StringUtils {
fun isNullOrEmpty(s: String?): Boolean {
return s == null || s.isEmpty()
}
fun capitalize(s: String?): String? {
if (isNullOrEmpty(s)) return s
return s!![0].uppercaseChar().toString() + s.substring(1)
}
}
// 进一步 Kotlin 化优化
fun String?.isNullOrEmpty(): Boolean = this == null || this.isEmpty()
fun String?.capitalize(): String? =
this?.takeIf { it.isNotEmpty() }
?.let { it[0].uppercaseChar() + it.substring(1) }共存策略(Coexistence Strategy)
在实际项目中,Java 和 Kotlin 长期共存是常态。
┌─────────────────────────────────────────────────────────────────┐
│ 混合项目结构示例 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ project/ │
│ ├── src/main/ │
│ │ ├── java/ ← 现有 Java 代码 │
│ │ │ ├── com/app/legacy/ │
│ │ │ └── com/app/utils/ │
│ │ └── kotlin/ ← 新 Kotlin 代码 │
│ │ ├── com/app/features/ │
│ │ └── com/app/extensions/ │
│ └── build.gradle.kts ← Kotlin DSL 构建脚本 │
│ │
└─────────────────────────────────────────────────────────────────┘共存最佳实践:
// 1. 用扩展函数增强 Java 类,而非继承
fun Date.toLocalDate(): LocalDate =
this.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
// 2. 用 Kotlin 包装 Java API,提供更友好的接口
class KotlinHttpClient(private val javaClient: OkHttpClient) {
suspend fun get(url: String): Response = withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).build()
javaClient.newCall(request).execute()
}
}
// 3. 在边界处处理平台类型(Platform Types)
fun processJavaList(list: MutableList<String>?) { // 明确可空性
list?.forEach { println(it) }
}📝 练习题
题目1: 以下哪个注解可以让 Kotlin 的伴生对象方法在 Java 中像静态方法一样调用?
A. @JvmField
B. @JvmStatic
C. @JvmOverloads
D. @JvmName
【答案】B
【解析】@JvmStatic 用于伴生对象(companion object)或命名对象(named object)中的方法,使其在字节码中生成真正的静态方法。这样 Java 代码可以用 ClassName.methodName() 直接调用,而不是 ClassName.Companion.methodName()。@JvmField 用于属性;@JvmOverloads 用于生成默认参数的重载;@JvmName 用于自定义名称。
题目2: 关于 Java 与 Kotlin 共存,以下说法错误的是:
A. 同一个项目中可以同时包含 .java 和 .kt 文件
B. Kotlin 代码可以直接调用 Java 类和方法
C. 将 Java 文件转换为 Kotlin 后必须立即删除原 Java 文件
D. 迁移过程可以是渐进式的,新功能优先用 Kotlin
【答案】C
【解析】Java 到 Kotlin 的迁移是渐进式的(incremental),不存在"必须立即删除"的要求。IntelliJ 的转换功能会自动处理文件替换。实际项目中,团队可以根据自己的节奏逐步迁移,Java 和 Kotlin 代码可以长期共存并相互调用。
多范式编程(Multi-Paradigm Programming)
Kotlin 是一门多范式语言(multi-paradigm language),融合了面向对象(OOP)、函数式(FP)和命令式(Imperative)编程的优点。
"Kotlin combines object-oriented and functional programming features, allowing you to use the best tool for each task."
┌─────────────────────────────────────────────────────────────────┐
│ Kotlin 的多范式融合 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 面向对象 OOP │ │
│ │ 类、继承、接口 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────────┐ ┌─────┐ │
│ │命令式│ │ Kotlin │ │函数式│ │
│ │ │───►│ 代码 │◄───│ FP │ │
│ │循环 │ │ │ │高阶 │ │
│ │状态 │ │ │ │函数 │ │
│ └─────┘ └─────────┘ └─────┘ │
│ │
│ "Use the right paradigm for the right problem" │
└─────────────────────────────────────────────────────────────────┘面向对象(Object-Oriented Programming)
Kotlin 完整支持 OOP,但比 Java 更简洁、更安全。
// 类与继承(Classes and Inheritance)
open class Animal(val name: String) { // open 才能被继承
open fun speak() = "..."
}
class Dog(name: String, val breed: String) : Animal(name) {
override fun speak() = "Woof!" // 必须显式 override
}
// 接口与多实现(Interfaces)
interface Runnable {
fun run()
val speed: Int // 接口可以有抽象属性
}
interface Swimmer {
fun swim() = println("Swimming...") // 接口可以有默认实现
}
class Duck : Animal("Duck"), Runnable, Swimmer {
override fun run() = println("Running at $speed")
override val speed = 5
override fun speak() = "Quack!"
}
// 数据类(Data Classes):专为"数据容器"设计
data class Point(val x: Int, val y: Int)
// 密封类(Sealed Classes):限制继承层次
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}函数式编程(Functional Programming)
Kotlin 提供一流的函数式编程支持:高阶函数(Higher-Order Functions)、Lambda 表达式、不可变数据。
// 函数是一等公民(First-Class Citizens)
val double: (Int) -> Int = { it * 2 }
val numbers = listOf(1, 2, 3, 4, 5)
// 高阶函数:函数作为参数或返回值
val doubled = numbers.map(double) // [2, 4, 6, 8, 10]
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val sum = numbers.reduce { acc, n -> acc + n } // 15
// 链式调用(Method Chaining)
val result = numbers
.filter { it > 2 }
.map { it * it }
.sortedDescending()
.take(2)
// [25, 16]常用高阶函数速查:
┌───────────────────────────────────────────────────────────────┐
│ 集合操作高阶函数 │
├────────────┬──────────────────────────────────────────────────┤
│ 函数 │ 作用 │
├────────────┼──────────────────────────────────────────────────┤
│ map │ 转换每个元素 Transform each element │
│ filter │ 过滤元素 Keep elements matching predicate │
│ reduce │ 累积为单值 Reduce to single value │
│ fold │ 带初始值的累积 Reduce with initial value │
│ flatMap │ 映射并展平 Map and flatten │
│ groupBy │ 分组 Group elements by key │
│ partition │ 分成两组 Split into two lists │
│ any/all │ 存在/全部满足 Check predicate │
│ find │ 查找第一个 Find first matching │
│ associate │ 转为 Map Convert to Map │
└────────────┴──────────────────────────────────────────────────┘// 实际示例:处理用户数据
data class User(val name: String, val age: Int, val city: String)
val users = listOf(
User("Alice", 28, "Beijing"),
User("Bob", 35, "Shanghai"),
User("Charlie", 28, "Beijing")
)
// 按城市分组,统计每个城市的平均年龄
val avgAgeByCity = users
.groupBy { it.city }
.mapValues { (_, users) -> users.map { it.age }.average() }
// {Beijing=28.0, Shanghai=35.0}命令式编程(Imperative Programming)
Kotlin 保留命令式风格,适用于需要明确控制流程和状态的场景。
// 传统循环仍然支持
fun findFirstNegative(numbers: List<Int>): Int? {
for (num in numbers) {
if (num < 0) {
return num
}
}
return null
}
// 可变状态在某些场景下更高效
fun buildString(items: List<String>): String {
val sb = StringBuilder() // 可变对象
for (item in items) {
sb.append(item).append(", ")
}
return sb.removeSuffix(", ").toString()
}范式融合的艺术(The Art of Fusion)
真正的 Kotlin 高手懂得在合适的场景选择合适的范式。
// 混合使用示例:一个简单的状态机
sealed class State {
object Idle : State()
data class Loading(val progress: Int) : State() // OOP: 数据类
data class Success(val data: String) : State()
data class Error(val exception: Throwable) : State()
}
class StateMachine {
private var currentState: State = State.Idle // 命令式: 可变状态
// 函数式: 使用 when 表达式和模式匹配
fun transition(event: Event): State {
currentState = when (currentState) {
is State.Idle -> when (event) {
is Event.Start -> State.Loading(0)
else -> currentState
}
is State.Loading -> when (event) {
is Event.Progress -> State.Loading(event.percent)
is Event.Complete -> State.Success(event.data)
is Event.Fail -> State.Error(event.error)
else -> currentState
}
else -> currentState
}
return currentState
}
}📝 练习题
题目1: 以下代码的输出是什么?
val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers
.filter { it % 2 == 1 }
.map { it * 2 }
.sum()
println(result)A. 15
B. 18
C. 30
D. 9
【答案】B
【解析】执行流程:filter { it % 2 == 1 } 保留奇数 [1, 3, 5] → map { it * 2 } 变为 [2, 6, 10] → sum() 求和 2 + 6 + 10 = 18。这是函数式编程中典型的链式调用(method chaining)模式。
题目2: Kotlin 被称为多范式语言,以下哪项不是其支持的编程范式?
A. 面向对象编程(Object-Oriented Programming)
B. 函数式编程(Functional Programming)
C. 逻辑编程(Logic Programming)
D. 命令式编程(Imperative Programming)
【答案】C
【解析】逻辑编程(Logic Programming)是 Prolog 等语言的特性,基于逻辑规则和推理。Kotlin 不直接支持这种范式。Kotlin 融合的三大范式是:面向对象(类、继承、多态)、函数式(高阶函数、Lambda、不可变性)、命令式(循环、可变状态、控制流)。
空安全哲学(Null Safety Philosophy)
十亿美元的错误(The Billion Dollar Mistake)
1965年,Tony Hoare 发明了空引用(null reference)。多年后,他公开道歉:
"I call it my billion-dollar mistake. It has caused innumerable errors, vulnerabilities, and system crashes." — Tony Hoare, 2009
┌─────────────────────────────────────────────────────────────────┐
│ NullPointerException 的代价 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ • Android 应用崩溃的 #1 原因 │
│ • 生产环境 bug 的主要来源之一 │
│ • 开发者需要大量防御性代码 │
│ • 代码审查时容易遗漏 │
│ │
│ 典型的 Java 防御代码: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ if (user != null) { │ │
│ │ if (user.getAddress() != null) { │ │
│ │ if (user.getAddress().getCity() != null) { │ │
│ │ return user.getAddress().getCity().getName();│ │
│ │ } │ │
│ │ } │ │
│ │ } │ │
│ │ return "Unknown"; │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘可空类型的意义(The Meaning of Nullable Types)
Kotlin 的解决方案:将可空性编码到类型系统中(Encode nullability in the type system)。
// 类型系统区分可空与非空
var name: String = "Kotlin" // 非空类型(Non-null type)
var nickname: String? = null // 可空类型(Nullable type)
// name = null // ❌ 编译错误!Type mismatch
nickname = null // ✅ 允许"The type system distinguishes between references that can hold null (nullable references) and those that cannot (non-null references)." — Kotlin 官方文档
┌─────────────────────────────────────────────────────────────────┐
│ Kotlin 的类型层次(简化版) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Any? │
│ (可空的顶层类型) │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ │ │ │
│ Any null │
│ (非空的顶层类型) (唯一值) │
│ │ │
│ ┌─────┴─────┐ │
│ │ │ │
│ String Int ... │
│ │ │ │
│ String? Int? ...(加 ? 变为可空版本) │
│ │
│ Nothing │
│ (所有类型的子类型) │
│ │
└─────────────────────────────────────────────────────────────────┘空安全操作符(Null Safety Operators)
Kotlin 提供了一套优雅的操作符来处理可空类型:
val user: User? = getUser()
// 1. 安全调用操作符(Safe Call Operator)?.
val cityName = user?.address?.city?.name // 任一环节为 null 则返回 null
// 2. Elvis 操作符(Elvis Operator)?:
val displayName = user?.name ?: "Anonymous" // null 时使用默认值
// 3. 非空断言(Non-null Assertion)!!
val length = user!!.name.length // ⚠️ 危险!null 时抛 NPE
// 4. 安全类型转换(Safe Cast)as?
val str: String? = value as? String // 转换失败返回 null 而非抛异常
// 5. let 作用域函数
user?.let { u ->
// 这个块内 u 是非空的
println("User: ${u.name}, Age: ${u.age}")
}对比 Java 的防御代码:
// Kotlin:优雅简洁
val cityName = user?.address?.city?.name ?: "Unknown"
// 等价的 Java 代码
String cityName;
if (user != null && user.getAddress() != null
&& user.getAddress().getCity() != null) {
cityName = user.getAddress().getCity().getName();
} else {
cityName = "Unknown";
}平台类型(Platform Types)
当 Kotlin 调用 Java 代码时,编译器无法知道 Java 返回值是否可能为 null。这时会出现平台类型(Platform Types)。
// Java 方法返回 String(可能为 null)
// public String getName() { return name; }
val name = javaObject.getName() // 类型是 String!(平台类型)
// String! 表示"可能为 null,也可能不为 null"
// 编译器不强制检查,但运行时可能 NPE
// 最佳实践:在边界处明确类型
val safeName: String? = javaObject.getName() // 明确声明为可空
val nonNullName: String = javaObject.getName() ?: "default"┌─────────────────────────────────────────────────────────────────┐
│ 处理平台类型的最佳实践 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Java 返回值 ──────► Platform Type (String!) │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ 视为非空 视为可空 使用 Elvis │
│ val s: String val s: String? val s = x ?: "" │
│ (危险!) (安全) (安全) │
│ │
│ 推荐:在调用 Java API 时,总是假设返回值可能为 null │
│ │
└─────────────────────────────────────────────────────────────────┘空安全的实践智慧
// ✅ 好的实践
class UserService {
// 返回类型明确表达语义
fun findUser(id: String): User? = database.find(id) // 可能找不到
fun getUser(id: String): User = // 保证存在
findUser(id) ?: throw UserNotFoundException(id)
}
// ✅ 使用 requireNotNull / checkNotNull
fun process(data: String?) {
val nonNullData = requireNotNull(data) { "Data cannot be null" }
// 后续 nonNullData 是非空的
}
// ❌ 避免过度使用 !!
fun bad(user: User?) {
val name = user!!.name // 如果 null 会崩溃,且错误信息不明确
}
// ✅ 使用 ?.let 或 if-check
fun good(user: User?) {
user?.let { println(it.name) }
// 或
if (user != null) {
println(user.name) // 智能转换,user 在这里是非空的
}
}📝 练习题
题目1: 以下代码的输出是什么?
val name: String? = null
val length = name?.length ?: -1
println(length)A. null
B. 0
C. -1
D. 抛出 NullPointerException
【答案】C
类型推断机制(Type Inference Mechanism)
Kotlin 拥有强大的 类型推断(Type Inference) 能力,让你写出简洁的代码而不牺牲类型安全。
"Kotlin has local type inference, meaning in many cases the type of a variable or the return type of a function can be inferred by the compiler." — Kotlin 官方文档
局部类型推断(Local Type Inference)
局部类型推断发生在局部变量、表达式、Lambda等上下文中。编译器根据初始化表达式或上下文自动推导类型。
// 变量类型推断(Variable Type Inference)
val name = "Kotlin" // 推断为 String
val count = 42 // 推断为 Int
val price = 19.99 // 推断为 Double
val flag = true // 推断为 Boolean
val list = listOf(1, 2, 3) // 推断为 List<Int>
// 等价于显式声明
val name: String = "Kotlin"
val count: Int = 42推断规则图解:
┌─────────────────────────────────────────────────────────────────┐
│ 局部类型推断流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ val x = expression │
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ 分析表达式的类型 │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ ▼ ▼ │
│ 字面量 函数返回值 │
│ "text"→String listOf(1,2)→List<Int> │
│ 42→Int mapOf("a" to 1)→Map<String,Int> │
│ 3.14→Double user.getName()→String (或 String?) │
│ │
│ 最终:x 的类型 = 表达式的类型 │
│ │
└─────────────────────────────────────────────────────────────────┘Lambda 中的类型推断:
// 完整写法
val double: (Int) -> Int = { x: Int -> x * 2 }
// 推断参数类型(从左侧函数类型推断)
val double: (Int) -> Int = { x -> x * 2 }
// 推断整个 Lambda 类型(从右侧表达式推断)
val double = { x: Int -> x * 2 } // 推断为 (Int) -> Int
// 使用 it(单参数 Lambda 的隐式名称)
val double: (Int) -> Int = { it * 2 }
// 链式调用中的推断
listOf(1, 2, 3)
.filter { it > 1 } // it 推断为 Int
.map { it.toString() } // it 推断为 Int,返回 List<String>
.joinToString() // 推断为 String复杂推断示例:
// 泛型类型推断(Generic Type Inference)
val map = mutableMapOf<String, MutableList<Int>>()
map["numbers"] = mutableListOf() // 值的类型从 map 的类型推断
// 推断链
val result = users
.filter { it.age > 18 } // List<User>
.groupBy { it.city } // Map<String, List<User>>
.mapValues { it.value.size } // Map<String, Int>
.maxByOrNull { it.value } // Map.Entry<String, Int>?
?.key // String?全局类型推断的边界(Limits of Type Inference)
Kotlin 使用的是局部类型推断(Local Type Inference)而非全局类型推断(Global Type Inference)。这意味着某些场景下必须显式声明类型。
┌─────────────────────────────────────────────────────────────────┐
│ 必须显式声明类型的场景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 公开 API 的返回类型(Public API Return Types) │
│ 2. 未初始化的变量(Uninitialized Variables) │
│ 3. 递归函数(Recursive Functions) │
│ 4. 某些复杂泛型场景(Complex Generic Scenarios) │
│ │
└─────────────────────────────────────────────────────────────────┘场景1:公开 API 必须声明返回类型
// ✅ private 函数可以推断返回类型
private fun calculateSum(a: Int, b: Int) = a + b // 推断为 Int
// ❌ public 函数必须显式声明返回类型(最佳实践)
// 虽然编译器允许推断,但官方强烈建议显式声明
public fun calculateSum(a: Int, b: Int): Int = a + b
// 原因:公开 API 的返回类型是契约的一部分
// 显式声明可以:
// - 作为文档
// - 防止意外更改 API
// - 提高代码可读性场景2:延迟初始化的变量
// ❌ 编译错误:必须声明类型或初始化
val name // Error: This variable must either have a type annotation or be initialized
// ✅ 方案1:提供初始值
val name = "default"
// ✅ 方案2:显式声明类型
val name: String
// ✅ 方案3:lateinit(仅用于 var 和非基本类型)
lateinit var name: String
// ✅ 方案4:lazy 委托
val name: String by lazy { computeName() }场景3:递归函数必须声明返回类型
// ❌ 编译错误:递归调用需要显式返回类型
fun factorial(n: Int) = if (n <= 1) 1 else n * factorial(n - 1)
// ↑ Error!
// ✅ 正确:显式声明返回类型
fun factorial(n: Int): Int = if (n <= 1) 1 else n * factorial(n - 1)
// ✅ 或者使用代码块形式
fun factorial(n: Int): Int {
return if (n <= 1) 1 else n * factorial(n - 1)
}场景4:复杂泛型需要帮助编译器
// 编译器可能无法推断某些复杂场景
val emptyList = listOf() // ❌ 类型是 List<Nothing>,可能不是你想要的
// ✅ 显式指定类型参数
val emptyList = listOf<String>()
val emptyList: List<String> = listOf()
val emptyList = emptyList<String>()
// 构建器模式可能需要类型帮助
val map = buildMap { // ❌ 可能推断失败
put("a", 1)
}
val map = buildMap<String, Int> { // ✅ 显式指定
put("a", 1)
}类型推断的最佳实践(Best Practices)
// ✅ DO: 局部变量充分利用类型推断
fun processData() {
val name = getName() // 简洁
val users = fetchUsers() // 清晰
val result = users.filter { it.active }
}
// ✅ DO: 公开 API 显式声明类型
class UserRepository {
fun findById(id: Long): User? { ... } // 明确返回可空
fun getAll(): List<User> { ... } // 明确返回列表
fun save(user: User): User { ... } // 明确返回保存后的实体
}
// ⚠️ CONSIDER: 复杂表达式考虑加类型注解增加可读性
val usersByCity: Map<String, List<User>> = users.groupBy { it.city }
// ❌ AVOID: 过度使用显式类型(噪音)
val name: String = "Kotlin" // 冗余
val count: Int = 42 // 冗余
val list: List<Int> = listOf(1,2,3) // 冗余┌─────────────────────────────────────────────────────────────────┐
│ 类型推断决策树 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 需要声明类型吗? │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ 公开 API? 延迟初始化? 递归函数? │
│ │ │ │ │
│ Yes→显式声明 Yes→显式声明 Yes→显式声明 │
│ │ │ │ │
│ └──────┬──────┴─────────────┘ │
│ ▼ │
│ 局部变量? │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ 初始值清晰? 表达式复杂? │
│ │ │ │
│ Yes→省略类型 Yes→考虑加类型注解 │
│ (增加可读性) │
│ │
└─────────────────────────────────────────────────────────────────┘📝 练习题
题目1: 以下哪种情况 Kotlin 编译器无法自动推断类型?
A. val name = "Kotlin"
B. val list = listOf(1, 2, 3)
C. fun factorial(n: Int) = if (n <= 1) 1 else n * factorial(n - 1)
D. val sum = { a: Int, b: Int -> a + b }
【答案】C
【解析】递归函数(Recursive Function)必须显式声明返回类型。因为编译器在推断 factorial 返回类型时需要知道 factorial(n-1) 的返回类型,形成循环依赖。正确写法是 fun factorial(n: Int): Int = ...。其他选项都能正常推断:A 是字符串字面量,B 是泛型函数调用,D 是 Lambda 表达式(参数类型已提供)。
题目2: 关于 Kotlin 类型推断,以下说法正确的是:
A. Kotlin 使用全局类型推断,可以推断任何位置的类型
B. 公开 API 的返回类型可以完全依赖推断,无需声明
C. 局部变量推荐利用类型推断简化代码
D. Lambda 参数必须显式声明类型
【答案】C
【解析】Kotlin 使用局部类型推断(Local Type Inference),推荐在局部变量中充分利用以简化代码。A 错误,Kotlin 不是全局推断;B 错误,公开 API 建议显式声明返回类型作为契约和文档;D 错误,Lambda 参数类型通常可以从上下文推断,如 list.map { it * 2 } 中的 it 类型由 list 元素类型推断得出。
本章小结(Chapter Summary)
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 第一章 Kotlin 入门 │
│ Chapter 1: Getting Started │
│ │
│ 知识地图 │
│ Knowledge Map │
│ │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│语言哲学 │ │历史发展 │ │基本概念 │
│Philosophy│ │History │ │Concepts │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
• 简洁性 • JetBrains • 表达式优先
Conciseness 2010 开始 Expression-
• 安全性 • 2016 v1.0 oriented
Safety • 2017 Android • 语句 vs 表达式
• 实用主义 首选语言 Statement vs
Pragmatism • 2024 K2 编译器 Expression
• 不可变性
Immutability
│ │ │
└─────────────────────┼────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Java互操作│ │ 多范式 │ │ 空安全 │
│Interop │ │Paradigms│ │Null Safe│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
• 100%兼容 • 面向对象 OOP • 十亿美元错误
• 渐进迁移 • 函数式 FP • ?. ?: !!
• 共存策略 • 命令式 • 平台类型
• @Jvm注解 Imperative Platform Types
│
▼
┌─────────────┐
│ 类型推断 │
│ Inference │
└──────┬──────┘
│
• 局部推断
• 推断边界
• 最佳实践关键语法速查(Quick Reference)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 变量声明 Variable Declaration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
val immutable = "不可重新赋值" // Immutable reference
var mutable = "可以重新赋值" // Mutable reference
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 空安全 Null Safety
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
val nullable: String? = null // 可空类型
val safe = nullable?.length // 安全调用 Safe call
val default = nullable ?: "N/A" // Elvis 操作符
val forced = nullable!! // 非空断言(慎用!)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 表达式 Expressions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
val max = if (a > b) a else b // if 表达式
val grade = when (score) { // when 表达式
in 90..100 -> "A"
in 80..89 -> "B"
else -> "C"
}
val result = try { // try 表达式
riskyOperation()
} catch (e: Exception) {
defaultValue
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 函数式编程 Functional Programming
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
val doubled = list.map { it * 2 }
val filtered = list.filter { it > 0 }
val sum = list.reduce { acc, x -> acc + x }
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 数据类 Data Class
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
data class User(val name: String, val age: Int)
val user = User("Alice", 25)
val copy = user.copy(age = 26) // 创建修改后的副本思维转变(Mindset Shift)
┌─────────────────────────────────────────────────────────────────┐
│ 从 Java 到 Kotlin 的思维转变 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Java 思维 Kotlin 思维 │
│ ───────── ────────── │
│ │
│ "我需要写 getter/setter" → "用属性和 data class" │
│ │
│ "null 检查是我的责任" → "类型系统帮我检查 null" │
│ │
│ "需要 if-else 赋值临时变量" → "if 是表达式,直接返回值" │
│ │
│ "写个工具类加静态方法" → "写扩展函数,更自然" │
│ │
│ "回调嵌套处理异步" → "协程让异步像同步" │
│ │
│ "继承一切" → "组合优于继承,接口委托" │
│ │
└─────────────────────────────────────────────────────────────────┘📝 综合练习
题目1: 综合本章内容,以下代码体现了 Kotlin 的哪些特性?
data class User(val name: String, val email: String?)
fun User.displayName() = name.uppercase()
fun main() {
val user = User("alice", null)
println(user.displayName()) // ALICE
println(user.email?.length ?: 0) // 0
}A. 只有数据类
B. 数据类 + 空安全
C. 数据类 + 空安全 + 扩展函数 + 类型推断
D. 只有扩展函数和空安全
【答案】C
【解析】这段代码展示了多个 Kotlin 特性:
-
数据类(Data Class):
data class User自动生成equals/hashCode/toString -
空安全(Null Safety):
email: String?声明可空,?.安全调用,?:Elvis 操作符 -
扩展函数(Extension Function):
fun User.displayName()为 User 类添加方法 -
类型推断(Type Inference):
val user = ...自动推断为User类型 这正是 Kotlin 多特性协同工作的典型示例。
题目2: 根据 Kotlin 的设计哲学,以下哪种做法最符合"实用主义(Pragmatism)"原则?
A. 强制所有项目立即从 Java 完全迁移到 Kotlin
B. 设计一套全新的构建系统替代 Gradle
C. 保持与 Java 100% 互操作,支持渐进式采用
D. 只支持函数式编程,禁止使用可变状态
【答案】C
【解析】实用主义(Pragmatism)的核心是解决实际问题而非追求理论完美。Kotlin 选择与 Java 100% 互操作,让团队可以:
-
在现有 Java 项目中逐步引入 Kotlin
-
新旧代码无缝协作
-
复用整个 Java 生态系统
这种务实的设计降低了采用门槛,是 Kotlin 成功的关键因素之一。A/B/D 都是"推倒重来"的激进做法,与实用主义相悖。