一. Kotlin基础语法
Kotlin 语法文档
基础篇
1. Kotlin 简介
Kotlin 是一种现代的静态类型编程语言,由 JetBrains 开发,运行在 JVM 上,也可编译为 JavaScript 或原生代码(Kotlin/Native)。它是 Google 官方推荐的 Android 开发语言。
核心优势
- 简洁:大幅减少样板代码,相比 Java 代码量可减少约 40%
- 安全:空安全设计从语言层面杜绝空指针异常
- 互操作:与 Java 100% 互通,可直接使用现有 Java 库
- 函数式:一等公民的函数支持,Lambda 表达式流畅自然
- 协程:轻量级并发,告别回调地狱
运行环境
| 环境 | 说明 |
|---|---|
| JVM | Android / 后端服务 |
| JavaScript | 前端开发 |
| Native | iOS / 桌面 / 嵌入式 |
💡 提示:Kotlin 完全兼容 Java,可以在现有 Java 项目中逐步引入 Kotlin 代码,无需重写整个项目。
2. 基本语法
包声明与导入
// 包声明(必须在文件顶部)
package com.example.demo
// 导入单个类
import kotlin.math.PI
// 导入包下所有内容
import kotlin.math.*
// 别名导入(解决命名冲突)
import java.util.Date as JavaDate
import java.sql.Date as SqlDate
// 使用
val now = JavaDate()
Hello World
fun main() {
println("Hello, Kotlin!")
}
Kotlin 文件以 .kt 为扩展名。main 函数是程序入口点。与 Java 不同,Kotlin 不需要将代码放在类中(脚本式写法),也不需要在每行末尾写分号。
变量声明
// 只读变量(类似 final,推荐优先使用)
val name: String = "Alice"
val age = 25 // 类型推断,编译器自动推导为 Int
// 可变变量
var count: Int = 0
count = 10 // var 允许重新赋值
// 常量(编译期确定)
const val MAX_SIZE = 100
| 关键字 | 含义 | 可变性 |
|---|---|---|
val |
只读变量(read-only) | 不可重新赋值,但对象内部状态可变 |
var |
可变变量(mutable) | 可重新赋值 |
const val |
编译期常量 | 仅限顶层或伴生对象,值必须编译期确定 |
基本数据类型
| 类型 | 位宽 | 示例 | 说明 |
|---|---|---|---|
| Byte | 8 位 | val b: Byte = 1 |
范围 -128 到 127 |
| Short | 16 位 | val s: Short = 100 |
范围 -32768 到 32767 |
| Int | 32 位 | val i = 100 |
最常用整数类型 |
| Long | 64 位 | val l = 100L |
大整数,后缀 L |
| Float | 32 位 | val f = 3.14f |
单精度浮点,后缀 f |
| Double | 64 位 | val d = 3.14 |
双精度浮点(默认) |
| Boolean | — | val b = true |
true / false |
| Char | 16 位 | val c = 'A' |
单字符,用单引号 |
| String | — | val s = "Hi" |
字符串,不可变 |
⚠️ 注意:Kotlin 中所有基本类型都是对象(没有 Java 中的
int和Integer之分),编译器会根据情况自动优化为原始类型。
字符串模板
val name = "Alice"
val age = 25
// 简单变量引用
val message = "Name: $name, Age: $age"
// 表达式(花括号包裹)
val info = "Next year: ${age + 1}"
// 原始字符串(三重引号,支持多行)
val json = """
{
"name": "$name",
"age": $age
}
"""
3. 条件控制
if 表达式
Kotlin 中 if 是表达式(有返回值),不是语句:
val max = if (a > b) a else b
// 带代码块的 if
val result = if (score >= 90) {
println("优秀")
"A"
} else if (score >= 80) {
println("良好")
"B"
} else {
"C"
}
when 表达式(替代 switch)
when (x) {
1 -> println("x == 1")
2, 3 -> println("x is 2 or 3") // 多值匹配
in 4..10 -> println("x in range") // 范围匹配
is String -> println("x is String") // 类型匹配
else -> println("otherwise") // 默认分支
}
when 也可以作为表达式返回值:
val description = when (x) {
in 0..17 -> "未成年"
in 18..64 -> "成年人"
else -> "老年人"
}
4. 循环
// for 循环 - 区间
for (i in 1..5) { // 包含 5(闭区间)
println(i) // 输出 1 2 3 4 5
}
for (i in 1 until 5) { // 不包含 5(半开区间)
println(i) // 输出 1 2 3 4
}
for (i in 5 downTo 1) { // 递减
println(i) // 输出 5 4 3 2 1
}
for (i in 1..10 step 2) { // 步长为 2
println(i) // 输出 1 3 5 7 9
}
// for 循环 - 集合
val list = listOf("a", "b", "c")
for (item in list) {
println(item)
}
for ((index, item) in list.withIndex()) {
println("$index: $item")
}
// while 循环
var x = 0
while (x < 5) {
println(x++)
}
// do-while(至少执行一次)
do {
println("至少执行一次")
} while (false)
5. 函数定义
// 普通函数
fun sum(a: Int, b: Int): Int {
return a + b
}
// 单表达式函数(自动推断返回类型)
fun multiply(a: Int, b: Int) = a * b
// 默认参数
fun greet(name: String = "World") {
println("Hello, $name!")
}
// 命名参数调用
greet(name = "Alice")
// 可变参数
fun printAll(vararg items: String) {
for (item in items) println(item)
}
printAll("a", "b", "c")
// 返回多个值(Pair / Triple / 数据类)
fun getMinMax(list: List<Int>): Pair<Int, Int> {
return Pair(list.min(), list.max())
}
val (min, max) = getMinMax(listOf(1, 2, 3))
6. 空安全(Null Safety)
Kotlin 的类型系统从语言层面区分可空和非空类型,从根本上减少空指针异常(NPE)。
// 非空类型(默认)—— 不能赋值为 null
var name: String = "Alice"
// name = null // 编译错误!
// 可空类型 —— 加 ? 后缀
var nullableName: String? = "Bob"
nullableName = null // 允许
// 1. 安全调用 ?.
val length = nullableName?.length // 返回 Int?(null 时返回 null)
// 2. Elvis 操作符 ?:(提供默认值)
val len = nullableName?.length ?: 0 // null 时返回 0
// 3. 非空断言 !!(慎用,可能抛 NPE)
val notNull = nullableName!! // 确定非空时使用
// 4. 安全类型转换 as?
val str: String? = value as? String // 转换失败返回 null
// 5. let 作用域函数
nullableName?.let {
println("Name length: ${it.length}") // 仅当非空时执行
}
| 操作符 | 名称 | 作用 |
|---|---|---|
?. |
安全调用 | 对象为 null 时返回 null,不抛异常 |
?: |
Elvis 操作符 | 左侧为 null 时返回右侧默认值 |
!! |
非空断言 | 强制认为非空,为 null 时抛 NPE |
as? |
安全转换 | 转换失败返回 null,不抛异常 |
💡 提示:优先使用
val+ 非空类型,尽量避免使用!!。如果某个值可能为空,用?.和?:优雅地处理。
7. 异常处理
Kotlin 中 try 是表达式,可以返回值。Kotlin 不区分受检异常和运行时异常——所有异常都是非受检的(无需在方法签名中声明 throws)。
// 基本 try-catch-finally
try {
val num = "abc".toInt()
} catch (e: NumberFormatException) {
println("格式错误: ${e.message}")
} finally {
println("清理资源(无论如何都会执行)")
}
// try 是表达式(可以返回值)
val result: Int? = try {
"123".toInt()
} catch (e: NumberFormatException) {
null // 解析失败返回 null
}
// 可以捕获多个异常类型
try {
// ...
} catch (e: IOException) {
println("IO 异常")
} catch (e: Exception) {
println("其他异常")
}
// throw 抛异常
fun validate(age: Int) {
if (age < 0) throw IllegalArgumentException("年龄不能为负数: $age")
}
💡 提示:如果与 Java 互操作,需要让 Java 看到受检异常时,使用
@Throws(IOException::class)注解。
8. 集合操作
集合类型
| 类型 | 不可变 | 可变 | 特点 |
|---|---|---|---|
| List | listOf() |
mutableListOf() |
有序、可重复 |
| Set | setOf() |
mutableSetOf() |
无序、不重复 |
| Map | mapOf() |
mutableMapOf() |
键值对 |
// 创建集合
val list = listOf(1, 2, 3, 4, 5)
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
val set = setOf("a", "b", "a") // 实际只有 "a", "b"
val map = mapOf("key1" to "value1", "key2" to "value2")
// 常用操作
val filtered = list.filter { it > 2 } // [3, 4, 5]
val mapped = list.map { it * 2 } // [2, 4, 6, 8, 10]
val sum = list.sum() // 15
val average = list.average() // 3.0
val grouped = list.groupBy { it % 2 } // {1=[1,3,5], 0=[2,4]}
// 链式调用
list.filter { it % 2 == 0 }
.map { it * it }
.sortedDescending()
.forEach { println(it) }
序列(Sequence)——惰性求值
List 的每一步操作都创建中间集合(急切求值);Sequence 只在终止操作时执行一次(惰性求值),数据量大时可避免创建大量中间集合。
// List 操作:每一步都创建中间集合(急切求值)
val result1 = list.filter { it > 2 } // 创建新 List
.map { it * 2 } // 再创建新 List
.toList()
// Sequence:惰性求值,只在终止操作时执行一次
val result2 = list.asSequence()
.filter { it > 2 }
.map { it * 2 }
.toList() // 终止操作,才真正执行
// 无限序列
val naturals = generateSequence(0) { it + 1 }
val firstTen = naturals.take(10).toList() // [0..9]
// yield 构建序列 *
val fibonacci = sequence {
var a = 0
var b = 1
yield(a)
yield(b)
while (true) {
val next = a + b
yield(next)
a = b
b = next
}
}
println(fibonacci.take(10).toList())
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
💡 提示:数据量大或操作链长时,优先使用 Sequence 避免创建大量中间集合。但元素很少时(<100),List 操作反而更快(无额外开销)。
9. 类与对象
类的基本结构
// 主构造函数(最常用写法)
class Person(
val name: String, // 自动生成属性 + getter
var age: Int, // 自动生成属性 + getter/setter
private val id: String // 私有属性
) {
// 初始化代码块
init {
println("Person created: $name, $age")
}
// 成员函数
fun introduce() {
println("Hi, I'm $name, $age years old")
}
}
// 使用
val person = Person("Alice", 25, "ID001")
person.introduce()
主构造 vs 次构造
class Person constructor(name: String) { // constructor 关键字可省略
val name: String
// 初始化块
init {
this.name = name
}
// 次构造函数(必须委托给主构造)
constructor(name: String, age: Int) : this(name) {
println("Age: $age")
}
}
// 更简洁的写法(推荐)
class Person(val name: String) {
constructor(name: String, age: Int) : this(name) {}
}
10. 可见性修饰符
| 修饰符 | 可见范围 |
|---|---|
public(默认) |
任何地方可见 |
internal |
同一模块内可见 |
protected |
当前类及子类可见 |
private |
当前类 / 当前文件内可见(顶层声明时) |
// 文件级私有(同一 .kt 文件内可见)
private val config = "secret"
// 类中的修饰符
open class Base {
public val a = 1 // 任何地方可见
internal val b = 2 // 同一模块可见
protected val c = 3 // 子类可见
private val d = 4 // 仅本类可见
}
class Derived : Base() {
fun access() {
println(c) // 可以访问 protected
// println(d) // 编译错误:private 不可见
}
}
11. 继承与抽象类
Kotlin 中类默认是 final 的,需要用 open 修饰才能被继承。
// 基类(open 修饰)
open class Animal(val name: String) {
open fun makeSound() {
println("$name makes a sound")
}
}
// 子类
class Dog(name: String, val breed: String) : Animal(name) {
override fun makeSound() {
super.makeSound()
println("$name barks: Woof!")
}
}
// 抽象类
abstract class Vehicle {
abstract fun start()
abstract fun stop()
// 抽象类可以有具体实现
fun honk() = println("Beep beep!")
}
class Car : Vehicle() {
override fun start() = println("Car started")
override fun stop() = println("Car stopped")
}
| 修饰符 | 含义 |
|---|---|
open |
允许类被继承 / 方法被重写 |
abstract |
抽象类或抽象方法,必须被实现 |
final |
禁止继承或重写(默认) |
override |
重写父类方法或属性 |
super |
调用父类实现 |
12. 接口
// 接口可以包含抽象方法和带默认实现的方法
interface Flyable {
fun fly()
// 默认实现
fun takeOff() {
println("Taking off...")
fly()
}
}
interface Swimmable {
fun swim()
}
// 接口中的属性
interface Named {
val name: String // 抽象属性
val displayName: String // 可以有 getter
get() = "Named: $name"
}
// 一个类可以实现多个接口
class Duck(override val name: String) : Flyable, Swimmable, Named {
override fun fly() = println("$name is flying")
override fun swim() = println("$name is swimming")
}
13. 多态与智能类型转换
open class Shape {
open fun draw() = println("Drawing shape")
}
class Circle : Shape() {
override fun draw() = println("Drawing circle")
fun radius() = 5.0
}
class Rectangle : Shape() {
override fun draw() = println("Drawing rectangle")
}
// 多态
fun renderShape(shape: Shape) {
shape.draw() // 实际调用子类方法
}
// 智能类型转换(is 检查后自动转换)
fun handleShape(shape: Shape) {
if (shape is Circle) {
// 编译器自动转换为 Circle 类型
println("Radius: ${shape.radius()}")
}
// when + smart cast
when (shape) {
is Circle -> println("It's a circle")
is Rectangle -> println("It's a rectangle")
}
}
14. 数据类与解构声明
数据类自动生成 equals()、hashCode()、toString()、copy() 和 componentN() 方法。
data class User(
val id: Long,
val name: String,
val email: String
)
// 自动生成的功能
val user1 = User(1, "Alice", "alice@example.com")
println(user1) // User(id=1, name=Alice, email=alice@example.com)
// copy 并修改部分属性
val user2 = user1.copy(email = "new@example.com")
// 解构声明(基于 componentN 函数)
val (id, name, email) = user1
println("$id, $name, $email")
// componentN 函数
val id = user1.component1()
val name = user1.component2()
解构声明
解构声明适用于任何提供了 componentN() 函数的类型,包括数据类、Pair、Triple、Map.Entry 等:
// Pair 解构
val (country, city) = Pair("China", "Shanghai")
// Triple 解构
val (x, y, z) = Triple(1, 2, 3)
// Map 遍历时解构
val map = mapOf("key1" to "val1", "key2" to "val2")
for ((key, value) in map) {
println("$key -> $value")
}
// 不需要的变量用 _ 跳过
val (_, email2) = user1 // 只要 email
⚠️ 注意:数据类的要求:主构造函数至少有一个参数;所有主构造参数必须用
val或var声明;不能是abstract/open/sealed/inner类。
15. 枚举类
// 简单枚举
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
// 带属性的枚举
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF);
fun hexString() = "#${rgb.toString(16)}"
}
// when + 枚举(编译器检查穷举)
fun describe(color: Color) = when (color) {
Color.RED -> "红色"
Color.GREEN -> "绿色"
Color.BLUE -> "蓝色"
}
💡 提示:枚举类的每个值都是单例对象,适合"固定选项 + 固定属性"的场景(如星期几、方向、状态码)。
16. 密封类
密封类用于表示受限的类层次结构,所有子类在编译期已知。配合 when 使用时编译器会检查是否覆盖了所有分支。
// 密封类(受限的类层次结构)
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val code: Int, val message: String) : Result()
object Loading : Result()
}
// when 必须覆盖所有分支(编译器检查)
fun handleResult(result: Result) = when (result) {
is Result.Success -> println("成功: ${result.data}")
is Result.Error -> println("错误: ${result.message}")
Result.Loading -> println("加载中...")
// 不需要 else,编译器确保穷举
}
| 对比 | 枚举类 | 密封类 |
|---|---|---|
| 实例 | 每个值为单例 | 子类可有多个不同状态实例 |
| 数据 | 每个值只能有固定属性 | 每个子类可携带不同数据 |
| 适用场景 | 固定选项(星期、方向) | 代数数据类型(网络结果、UI 状态) |
17. 对象声明与伴生对象
// 对象声明 → 单例模式(线程安全)
object DatabaseConfig {
val url = "jdbc:mysql://localhost:3306/mydb"
val username = "root"
private val password = "password"
fun connect() {
println("Connecting to $url")
}
}
// 使用
DatabaseConfig.connect()
// 伴生对象 → 类的"静态"成员
class MyClass {
companion object Factory {
const val VERSION = "1.0.0"
fun create(): MyClass = MyClass()
// 生成 JVM 静态方法(Java 可直接通过类名调用)
@JvmStatic
fun staticMethod() = println("Static call (JVM)")
// Kotlin 中两种方式调用一样,但 Java 中需要通过 Companion 访问
fun instanceMethod() = println("Companion instance call")
}
}
// 使用
MyClass.create()
MyClass.VERSION
MyClass.staticMethod()
💡 提示:在 Kotlin 中,
@JvmStatic不影响 Kotlin 调用方式;它的作用是让 Java 代码可以通过MyClass.staticMethod()而非MyClass.Companion.staticMethod()调用。
18. 扩展函数与作用域函数
扩展函数
无需继承即可为已有类添加方法:
// 扩展函数 - 为已有类添加方法
fun String.addExclamation(): String = this + "!"
println("Hi".addExclamation()) // Hi!
// 带参数的扩展函数
fun String.repeat(n: Int): String = this.repeat(n)
println("Go".repeat(3)) // GoGoGo
// 泛型扩展函数
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null
// 扩展属性
val String.wordCount: Int
get() = this.split(" ").size
作用域函数
| 函数 | 上下文对象 | 返回值 | 典型用途 |
|---|---|---|---|
let |
it |
Lambda 结果 | 空安全处理、链式类型转换 |
apply |
this |
上下文对象 | 对象初始化/配置 |
also |
it |
上下文对象 | 附加副作用(日志等) |
run |
this |
Lambda 结果 | 对象操作 + 返回计算结果 |
with |
this |
Lambda 结果 | 对同一个对象做多个操作 |
// apply:配置对象,返回对象本身
val person = Person().apply {
name = "Tom"
age = 30
}
// let:空安全 + 链式转换
val length = "hello".let { it.uppercase() }.let { it.length }
// also:附加副作用
val result = "hello"
.uppercase()
.also { println("转换结果: $it") }
// run:对象操作 + 返回计算结果
val info = person.run {
"Name: $name, Age: $age"
}
// with:对同一个对象执行多个操作
with(person) {
introduce()
println(age)
}
19. 属性高级用法
class PropertyExample {
// 自定义 getter/setter
var customProperty: String = "default"
get() = field.uppercase() // field = 幕后字段
set(value) {
if (value.isNotEmpty()) {
field = value.trim()
}
}
// 计算属性(无幕后字段,每次访问重新计算)
val computedProperty: Int
get() = (1..100).random()
// 延迟初始化(依赖注入场景)
lateinit var lateInitProp: String
// 惰性初始化(首次访问时才计算,线程安全)
val lazyProp: String by lazy {
println("首次访问才计算")
"Expensive result"
}
}
进阶篇
20. 中缀函数
用 infix 关键字标记的函数可以通过中缀表示法调用(省略点和括号),使代码更接近自然语言。
// 自定义中缀函数
infix fun String.onto(other: String) = Pair(this, other)
// 中缀调用
val pair = "key" onto "value" // 等价于 "key".onto("value")
println(pair) // (key, value)
// 中缀函数必须满足:
// - 是成员函数或扩展函数
// - 只有一个参数
// - 参数不能是可变参数,不能有默认值
Kotlin 内置的中缀函数(前面已经用过了):
| 中缀函数 | 示例 | 说明 |
|---|---|---|
to |
"key" to "value" |
创建 Pair |
until |
1 until 5 |
半开区间 [1, 5) |
downTo |
5 downTo 1 |
递减区间 |
step |
1..10 step 2 |
步长 |
// 更多自定义示例
class Person(val name: String) {
infix fun says(message: String) {
println("$name says: $message")
}
}
Person("Alice") says "Hello!" // Alice says: Hello!
21. 操作符重载
Kotlin 允许为自定义类型重载标准操作符,让自定义类型的运算像原生类型一样自然。
data class Point(val x: Int, val y: Int) {
// 加法
operator fun plus(other: Point) = Point(x + other.x, y + other.y)
// 减法
operator fun minus(other: Point) = Point(x - other.x, y - other.y)
// 取反
operator fun unaryMinus() = Point(-x, -y)
// 下标访问
operator fun get(index: Int) = when (index) {
0 -> x
1 -> y
else -> throw IndexOutOfBoundsException()
}
// 判断包含
operator fun contains(p: Point) = x == p.x && y == p.y
}
val p1 = Point(1, 2)
val p2 = Point(3, 4)
val p3 = p1 + p2 // Point(4, 6)
val p4 = p2 - p1 // Point(2, 2)
val neg = -p1 // Point(-1, -2)
val x = p1[0] // 1(下标访问)
val hasPoint = p1 in listOf(p1, p2) // contains
常用可重载操作符
| 操作符 | 函数名 | 示例 |
|---|---|---|
+ |
plus |
a + b |
- |
minus |
a - b |
* |
times |
a * b |
/ |
div |
a / b |
% |
rem |
a % b |
++ |
inc |
a++ |
-- |
dec |
a-- |
[] |
get / set |
a[i] / a[i] = v |
in |
contains |
a in b |
() |
invoke |
a() |
== |
equals |
a == b |
> < |
compareTo |
a > b |
.. |
rangeTo |
a..b |
22. 高阶函数与 Lambda
高阶函数是指接受函数作为参数或返回函数的函数。这是函数式编程的核心。
// 函数类型作为参数
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// 使用 Lambda
val sum = operateOnNumbers(5, 3) { x, y -> x + y } // 8
val product = operateOnNumbers(5, 3) { x, y -> x * y } // 15
// 函数引用
fun isEven(num: Int) = num % 2 == 0
val numbers = listOf(1, 2, 3, 4)
val evens = numbers.filter(::isEven) // [2, 4]
// 返回函数的函数
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
val triple = makeMultiplier(3)
println(triple(10)) // 30
| Lambda 语法 | 含义 |
|---|---|
{ x -> x * 2 } |
单参数,显式命名 |
{ it * 2 } |
单参数,隐式名称 it |
{ x, y -> x + y } |
多参数 |
::functionName |
函数引用 |
this::methodName |
成员方法引用 |
ClassName::methodName |
类方法引用 |
内联函数
inline 将 Lambda 体直接嵌入调用处,避免创建匿名对象,减少运行时开销:
// 内联函数
inline fun measureTime(action: () -> Unit) {
val start = System.currentTimeMillis()
action()
println("耗时: ${System.currentTimeMillis() - start}ms")
}
// 不希望内联的 Lambda 参数用 noinline
inline fun execute(inlineBlock: () -> Unit, noinline noInlineBlock: () -> Unit) {
inlineBlock() // 内联
noInlineBlock() // 不内联,可以作为参数传递
}
// crossinline:禁止非局部返回(内联 + 不允许 return)
inline fun safeRun(crossinline block: () -> Unit) {
val runnable = Runnable { block() } // 跨执行上下文时使用
runnable.run()
}
| 修饰符 | 作用 |
|---|---|
inline |
Lambda 内联到调用处,允许非局部 return |
noinline |
指定某个 Lambda 参数不内联 |
crossinline |
内联但禁止非局部 return(跨执行上下文时) |
23. 类型别名
typealias 为现有类型创建别名,提高代码可读性。
// 函数类型别名
typealias ClickHandler = (View) -> Unit
typealias Predicate<T> = (T) -> Boolean
// 泛型类型别名
typealias UserMap = Map<Long, User>
typealias StringTable = Map<String, Map<String, String>>
// 内部类别名
typealias Handler = MyClass.MyInnerClass
// 嵌套类别名
typealias NodeSet = Set<Network.Node>
// 使用(与原名完全等价)
fun setClickListener(handler: ClickHandler) { /* ... */ }
val users: UserMap = mapOf()
24. 类型系统:Any / Unit / Nothing
Kotlin 的类型层次非常简洁清晰:
Any? (可空类型的根)
/ \
Any Nothing?
| |
(所有非空类型) Nothing (所有类型的子类型)
|
┌───┼───┬──────┬─────┐
Int String List Boolean ...
Any:所有非空类型的根,等价于 Java 的Object(但不含wait/notify)Any?:所有可空类型的根Nothing:所有类型的子类型(底类型),表示"永不存在的值"
// Any —— 所有非空类型的超类
val any: Any = "hello"
val any2: Any = 42
// Unit —— 对应 Java void(但 Unit 是真正的对象)
fun log(msg: String): Unit { // Unit 返回类型可省略
println(msg)
}
val unit: Unit = log("hi") // Unit 只有唯一实例
// Nothing —— 永远不会返回(throw 或无限循环)
fun fail(message: String): Nothing = throw IllegalArgumentException(message)
fun infiniteLoop(): Nothing {
while (true) { /* ... */ }
}
// TODO() 的返回类型就是 Nothing(占位用)
fun notYetImplemented(): String = TODO("待实现")
// Nothing 是任何类型的子类型,所以可以用于:
val x: String = fail("error") // 编译通过!Nothing 可以赋值给任何类型
val y: String? = null // null 的类型是 Nothing?
💡 提示:理解
Nothing有助于理解空安全——null的类型是Nothing?,因此可以赋值给任何可空类型。TODO()返回Nothing,因此可以放在任何表达式位置充当占位符。
实用篇
25. 文件操作
Kotlin 对 java.io.File 做了大量扩展,读写文件比 Java 简洁很多。
import java.io.File
// 读取整个文件为字符串
val content = File("input.txt").readText()
// 写入字符串到文件
File("output.txt").writeText("Hello, Kotlin!")
// 按行读取(处理大文件时不会一次性加载全部内容)
File("data.txt").useLines { lines ->
lines.filter { it.isNotBlank() }
.forEach { println(it) }
}
// 遍历目录树
File("src").walk()
.filter { it.extension == "kt" }
.forEach { println(it.name) }
// 复制文件
File("source.txt").copyTo(File("dest.txt"), overwrite = true)
// 递归删除目录
File("temp").deleteRecursively()
// use 自动关闭资源(替代 Java 的 try-with-resources)
File("data.txt").bufferedReader().use { reader ->
reader.forEachLine { println(it) }
}
// 创建临时文件
val tempFile = File.createTempFile("prefix", ".tmp")
tempFile.deleteOnExit()
大文件复制 / 移动(带进度监听)
copyTo 和 deleteRecursively 是一次性操作,无法监听进度。处理大文件时,可以手动分块读写实现进度回调:
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
// 带进度回调的大文件复制
fun File.copyWithProgress(
dest: File,
bufferSize: Int = 8 * 1024, // 8KB 缓冲区
onProgress: (copied: Long, total: Long) -> Unit // 进度回调
) {
val total = this.length()
var copied = 0L
FileInputStream(this).use { input ->
FileOutputStream(dest).use { output ->
val buffer = ByteArray(bufferSize)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1) {
output.write(buffer, 0, bytesRead)
copied += bytesRead
onProgress(copied, total) // 通知调用方
}
}
}
}
// 使用示例
val source = File("large_file.iso")
val dest = File("copy.iso")
source.copyWithProgress(dest) { copied, total ->
val percent = (copied * 100 / total)
val copiedMB = copied / 1024 / 1024
val totalMB = total / 1024 / 1024
print("\r进度: $percent% ($copiedMB / $totalMB MB)") // \r 覆盖当前行
}
println() // 换行
💡 提示:移动文件本质上就是
copyWithProgress+ 删除源文件。如果需要更丰富的进度信息(速度、剩余时间等),可以用kotlinx.coroutines的flow将进度做成Flow<Progress>,在协程中驱动 UI 更新。
26. 正则表达式
Kotlin 的原始字符串("""...""")与正则结合是绝配——无需像 Java 那样疯狂转义反斜杠。
// 创建正则(原始字符串避免转义)
val phoneRegex = Regex("""1[3-9]\d{9}""")
val dateRegex = """\d{4}-\d{2}-\d{2}""".toRegex()
// 判断是否包含匹配
val hasPhone = phoneRegex.containsMatchIn("手机: 13812345678") // true
// 查找第一个匹配
val match = phoneRegex.find("A: 13811112222, B: 13933334444")
println(match?.value) // 13811112222
// 查找所有匹配
phoneRegex.findAll("A: 13811112222, B: 13933334444").forEach {
println(it.value)
}
// 分组捕获(括号分组)
val birthdayRegex = """(\d{4})-(\d{2})-(\d{2})""".toRegex()
val result = birthdayRegex.find("生日: 1995-08-20")
result?.let {
val (year, month, day) = it.destructured
println("${year}年${month}月${day}日") // 1995年08月20日
}
// groupValues 索引 0 是完整匹配,1/2/3 是分组
println(result?.groupValues?.get(1)) // 1995
// 替换
val masked = phoneRegex.replace("13812345678") {
it.value.replaceRange(3, 7, "****")
}
println(masked) // 138****5678
// 拆分字符串
val words = Regex("""\s+""").split("Kotlin is great")
println(words) // [Kotlin, is, great]
27. JSON 序列化
Kotlin 官方提供的 kotlinx.serialization 是编译期安全的序列化方案,无需反射。
依赖:
org.jetbrains.kotlinx:kotlinx-serialization-json插件:kotlin("plugin.serialization")
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.SerialName
// 标记可序列化的数据类
@Serializable
data class User(
val id: Int,
val name: String,
val email: String
)
// 序列化:对象 → JSON 字符串
val user = User(1, "Alice", "alice@example.com")
val json = Json.encodeToString(user)
println(json) // {"id":1,"name":"Alice","email":"alice@example.com"}
// 反序列化:JSON 字符串 → 对象
val jsonStr = """{"id":2,"name":"Bob","email":"bob@example.com"}"""
val parsed = Json.decodeFromString<User>(jsonStr)
println(parsed.name) // Bob
// 自定义 JSON 配置
val prettyJson = Json {
prettyPrint = true // 格式化输出
ignoreUnknownKeys = true // 忽略 JSON 中多出的字段
isLenient = true // 宽松模式(允许 JSON 注释等)
encodeDefaults = true // 输出默认值(否则不写)
}
println(prettyJson.encodeToString(user))
// {
// "id": 1,
// "name": "Alice",
// "email": "alice@example.com"
// }
// 字段名映射(驼峰 ↔ 蛇形)
@Serializable
data class ApiUser(
@SerialName("user_id") val id: Int,
@SerialName("user_name") val name: String,
@SerialName("is_active") val isActive: Boolean
)
val apiJson = """{"user_id":1,"user_name":"Alice","is_active":true}"""
val apiUser = Json.decodeFromString<ApiUser>(apiJson)
println(apiUser.isActive) // true
// 嵌套对象 + 列表
@Serializable
data class ApiResponse(
val code: Int,
val message: String,
val data: List<User>
)
28. 日期时间
Kotlin 直接使用强大的 java.time 包,比老旧的 Date 和 Calendar 好用得多。
import java.time.*
import java.time.format.DateTimeFormatter
// 获取当前时间
val now = LocalDateTime.now()
val today = LocalDate.now()
val currentTime = LocalTime.now()
// 创建指定日期
val date = LocalDate.of(2024, 3, 15)
val date2 = LocalDate.parse("2024-03-15")
// 日期运算
val tomorrow = today.plusDays(1)
val lastWeek = today.minusWeeks(1)
val nextMonth = today.plusMonths(1)
val isAfter = date.isAfter(today) // 日期比较
// 格式化输出
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")
println(now.format(formatter)) // 2024年03月15日 14:30:00
// 解析字符串
val parsed = LocalDate.parse("2024-03-15", DateTimeFormatter.ISO_LOCAL_DATE)
// 计算两个日期之间的间隔
val period = Period.between(date, today)
println("${period.years}年${period.months}月${period.days}天")
// 精确时间差
val start = Instant.now()
// ... 执行操作 ...
val end = Instant.now()
val duration = Duration.between(start, end)
println("耗时: ${duration.toMillis()}ms")
// 时区处理
val shanghaiNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
val tokyoTime = shanghaiNow.withZoneSameInstant(ZoneId.of("Asia/Tokyo"))
println("上海: $shanghaiNow, 东京: $tokyoTime")
29. 网络请求(Ktor Client)
Ktor 是 Kotlin 官方的 HTTP 客户端,基于协程设计,轻量且简洁。
依赖:
io.ktor:ktor-client-core+ 引擎(如io.ktor:ktor-client-cio或ktor-client-okhttp)
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
val client = HttpClient()
// GET 请求
suspend fun fetchData(): String {
val response: HttpResponse = client.get("https://api.example.com/data")
return response.bodyAsText()
}
// GET 请求(带查询参数和 Header)
suspend fun search(query: String): String {
val response = client.get("https://api.example.com/search") {
parameter("q", query) // ?q=kotlin
parameter("page", 1) // &page=1
header("Authorization", "Bearer token_xxx")
}
return response.bodyAsText()
}
// POST 请求(发送 JSON)
@Serializable
data class CreateUser(val name: String, val email: String)
suspend fun createUser(user: CreateUser): String {
val response = client.post("https://api.example.com/users") {
contentType(ContentType.Application.Json)
setBody(user) // 自动序列化为 JSON
}
return response.bodyAsText()
}
// 上传文件
suspend fun uploadFile(file: File): String {
val response = client.submitFormWithBinaryData(
url = "https://api.example.com/upload",
formData = formData {
append("file", file.readBytes(), Headers.build {
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
})
}
)
return response.bodyAsText()
}
// 使用
fun main() = runBlocking {
val data = fetchData()
println(data)
client.close() // 程序退出前关闭客户端
}
高级篇
30. 委托模式 *
类委托
interface Repository {
fun save(data: String)
fun load(): String
}
class DatabaseRepository : Repository {
override fun save(data: String) = println("Saving: $data")
override fun load(): String = "Data from DB"
}
// 委托给另一个对象,可选择性重写
class CacheRepository(db: Repository) : Repository by db {
override fun save(data: String) {
println("Caching: $data")
// db.save(data) // 也可以调用被委托对象
}
}
属性委托
import kotlin.properties.Delegates
class Person {
// 可观察属性
var name: String by Delegates.observable("<no name>") {
_, old, new -> println("$old → $new")
}
// 可否决属性
var age: Int by Delegates.vetoable(0) {
_, _, new -> new >= 0 // 拒绝负数
}
// 延迟初始化
val lazyValue: String by lazy {
println("计算一次")
"Result"
}
}
31. 泛型高级用法 *
// 协变(out):只能作为返回值(生产者)
interface Producer<out T> {
fun produce(): T
}
// 逆变(in):只能作为参数(消费者)
interface Consumer<in T> {
fun consume(item: T)
}
// 星投影
fun printList(list: List<*>) {
list.forEach { println(it) }
}
// reified 具体化类型参数(需 inline)
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T
}
// 使用:T 在运行时可见
val result = isInstance<String>("hello") // true
// 泛型约束
fun <T : Comparable<T>> sort(list: List<T>) { /* ... */ }
// 多个约束(where 子句)
fun <T> copyIfGreater(list: List<T>, threshold: T): List<T>
where T : Comparable<T>, T : Cloneable {
return list.filter { it > threshold }
}
| 修饰符 | 名称 | 规则 | 示例 |
|---|---|---|---|
out T |
协变 | 只能出现在输出位置(返回值) | Producer<out T> |
in T |
逆变 | 只能出现在输入位置(参数) | Consumer<in T> |
| 不变 | 默认 | 同时出现在输入和输出 | List<T> |
32. 协程(Coroutines)
协程是 Kotlin 的轻量级并发解决方案。一个线程可以运行成千上万个协程,协程的切换开销远小于线程。
依赖:
org.jetbrains.kotlinx:kotlinx-coroutines-core
基础用法
import kotlinx.coroutines.*
// 基础:launch 启动一个协程
fun main() = runBlocking {
launch {
delay(1000L) // 非阻塞挂起
println("World!")
}
println("Hello,")
}
// 输出:Hello, →(等1秒)→ World!
// suspend 挂起函数
suspend fun fetchData(): String {
delay(2000L)
return "数据加载完成"
}
// 异步并发:async + await
suspend fun loadAll() = coroutineScope {
val job1 = async { fetchUserInfo() }
val job2 = async { fetchUserPosts() }
val user = job1.await()
val posts = job2.await()
return@coroutineScope Pair(user, posts)
}
调度器与上下文
// 常用调度器
launch(Dispatchers.IO) { // IO 密集型(网络/文件)
val data = fetchFromNetwork()
}
launch(Dispatchers.Main) { // UI 线程(Android)
updateUI(data)
}
launch(Dispatchers.Default) { // CPU 密集型计算
val result = heavyComputation()
}
// 自定义线程池
val customContext = newSingleThreadContext("CustomThread")
launch(customContext) { /* ... */ }
Flow —— 响应式流
// 创建 Flow
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i) // 发射数据
}
}
// 收集 Flow
runBlocking {
simpleFlow().collect { value ->
println(value) // 1, 2, 3(间隔100ms)
}
}
// Flow 操作符
simpleFlow()
.map { it * 2 }
.filter { it > 2 }
.onEach { println("Processing: $it") }
.catch { e -> println("Error: $e") }
.collect()
| 概念 | 说明 |
|---|---|
CoroutineScope |
协程作用域,管理协程生命周期 |
launch |
启动协程,不返回结果(返回 Job) |
async |
启动协程,返回 Deferred(可通过 await 获取结果) |
suspend |
挂起函数标记,可在协程中调用 |
delay |
非阻塞延迟(挂起函数) |
Flow |
冷流,类似响应式流(RxJava 的 Observable) |
Channel |
协程间通信(类似 BlockingQueue) |
Mutex |
协程互斥锁 |
33. DSL 构建器 *
Kotlin 的 Lambda 与接收者类型结合,可以构建优雅的领域特定语言(DSL)。
// HTML DSL 示例
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
class HTML {
private val children = mutableListOf<String>()
fun body(init: Body.() -> Unit) {
val body = Body()
body.init()
children.add("<body>${body.content}</body>")
}
override fun toString() = "<html>${children.joinToString("")}</html>"
}
class Body {
var content: String = ""
fun h1(text: String) { content += "<h1>$text</h1>" }
fun p(text: String) { content += "<p>$text</p>" }
}
// 使用 DSL —— 像写 HTML 一样自然
val page = html {
body {
h1("标题")
p("段落内容")
}
}
34. 注解与反射
// 自定义注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnnotation(val description: String)
// 使用反射
import kotlin.reflect.full.*
@MyAnnotation("测试类")
class TestClass {
@MyAnnotation("测试方法")
fun testMethod() {}
}
fun analyze(obj: Any) {
val kClass = obj::class
val annotation = kClass.findAnnotation<MyAnnotation>()
println(annotation?.description)
kClass.memberFunctions.forEach { function ->
val funcAnnotation = function.findAnnotation<MyAnnotation>()
println("方法: ${function.name}, 描述: ${funcAnnotation?.description}")
}
}
35. Kotlin 与 Java 互操作 *
Kotlin 与 Java 可以无缝互调用,是实际项目中的必备知识。
从 Java 调用 Kotlin
// Kotlin 侧 —— 为 Java 调用优化
class KotlinLib {
companion object {
// 生成真正的 JVM 静态方法
@JvmStatic
fun create(): KotlinLib = KotlinLib()
// 普通伴生对象方法(Java 需通过 Companion 调用)
fun helper() = println("helper")
}
// 为有默认参数的函数生成重载方法
@JvmOverloads
fun greet(name: String = "World", times: Int = 1) {
repeat(times) { println("Hello, $name!") }
}
// 将属性暴露为 JVM 字段(而非 getter/setter)
@JvmField
val CONSTANT = 42
// 修改生成的 Java 方法名(解决签名冲突)
@JvmName("getFullName")
fun getName(): String = "full name"
}
// Java 侧调用
KotlinLib.create(); // @JvmStatic 生效
KotlinLib.Companion.helper(); // 无注解需通过 Companion
KotlinLib lib = new KotlinLib();
lib.greet("Alice"); // @JvmOverloads 生成重载
lib.greet("Alice", 3);
int value = KotlinLib.CONSTANT; // @JvmField 直接访问字段
从 Kotlin 调用 Java
// 平台类型 String! —— 空安全信息未知
val result = someJavaMethod() // 返回类型是 String!(平台类型)
// 你需要自己判断是否可空:
val safe: String? = result // 当作可空处理
val assert: String = result // 当作非空处理(result 为 null 时抛异常)
// SAM 转换(单抽象方法接口自动转为 Lambda)
// Java 接口:
// public interface OnClickListener {
// void onClick(View v);
// }
button.setOnClickListener { view -> println("Clicked!") }
// 访问 Java getter/setter(Kotlin 自动识别为属性访问)
val name = file.getName() // Java 写法
val name = file.name // Kotlin 属性语法(等价)
常用互操作注解速查
| 注解 | 作用 |
|---|---|
@JvmStatic |
伴生对象方法生成真正的静态方法 |
@JvmOverloads |
为默认参数生成 Java 重载方法 |
@JvmField |
属性暴露为直接字段访问 |
@JvmName |
修改生成的字节码方法名 |
@JvmSynthetic |
对 Java 隐藏该方法 |
@Throws |
声明可能抛出的受检异常 |
36. Kotlin Multiplatform *
// commonMain —— 公共代码
expect fun platformName(): String
expect class PlatformLogger {
fun log(message: String)
}
// androidMain —— Android 实现
actual fun platformName(): String = "Android"
actual class PlatformLogger {
actual fun log(message: String) {
Log.d("KMP", message)
}
}
// iosMain —— iOS 实现
actual fun platformName(): String = "iOS"
actual class PlatformLogger {
actual fun log(message: String) {
println("[iOS] $message")
}
}