Blog
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)

HiuZing

🍑
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)
  • Kotlin入门
    • 基本格式
    • 变量
    • 字符串模板
    • 基本类型
    • 集合
      • List
      • Set
      • Map
    • 控制流
      • if
      • When
      • Ranges
      • 循环
      • for
      • While
    • 函数
      • 参数
      • 默认参数
      • 不返回值的函数
      • 单表达式函数
      • Lambda 表达式
    • 面向对象
      • 定义
      • 创建实例
      • 访问属性
      • 成员函数
      • init 初始化块
      • 主构造函数和次构造函数
      • 继承
      • 接口Interface
      • 数据类data class
      • object单例
      • companion object 伴生对象
      • enum枚举
    • 空安全
      • 检查是否存在空值
      • 使用安全调用函数
      • 使用 Elvis 操作符
      • !! 非空断言
      • ?.let { }
    • 扩展函数
      • 扩展属性
      • 作用域函数
    • 协程
      • suspend 挂起函数
      • launch 和 async 区别
      • 协程并发执行
      • Dispatchers 调度器
      • 取消协程
  • Kotlin
HiuZing
2026-08-14
目录

Kotlin入门

# 基本格式

fun main(){
    
}
1
2
3

# 变量

val 只读变量

var 可变性变量

# 字符串模板

放在花括号 {} 内,即位于美元符号 $ 之后

// 1. 嵌入变量:$变量名
val customers = 10
println("There are $customers customers")

// 2. 嵌入表达式:${表达式}
println("There are ${customers + 1} customers")

// 3. 多行字符串(三个引号)
val s3 = """
    姓名:$name
    年龄:$age
""".trimIndent()

// 例子
"Bearer $token" → 拼接 Authorization 请求头
"已连续打卡" + "$clockInCount 天" → 打卡天数显示
"最难:${it.stem}(浏览${it.views})" → 题目信息拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 基本类型

// 整型
val year: Int = 2020
// 长整型
val amount: Long = 350_000_000

// 无符号整数
val score: UInt = 100u

// 浮点型
val currentTemp: Float = 24.5f
val price: Double = 19.99

// 布尔值
val isEnabled: Boolean = true

// 字符
val separator: Char = ','

// 字符串
val message: String = "Hello, world!"

例子:
Int → 题目难度:difficulty: Int = 0,浏览量:views: Int = 0
String → 用户名:username: String = "",题目标题:stem: String
Boolean → 打卡状态:flag: Boolean
Long → 时间戳:startTime: Long、endTime: Long
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

类型推断

一般不需要手动声明

类型转换

val str = "123"
val num = str.toInt()
1
2

# 集合

# List

有序的,并且允许包含重复项

// 只读
val readOnlyShapes = listOf("triangle", "square", "circle")
println(readOnlyShapes)
// [triangle, square, circle]

// 可修改
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
println(shapes)
// [triangle, square, circle]

// casting
// 将可变的列表赋值给一个 List 类型的变量,就可以变成只读
val shapes: MutableList<String> = mutableListOf("triangle", "square", "circle")
val shapesLocked: List<String> = shapes

// 访问元素
readOnlyShapes[0]

// 首页轮播图
listOf(
    R.drawable.banner_ai,
    R.drawable.banner_pj,
    R.drawable.banner_qa
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Set

集合是无序的,只存储唯一项

// 只读
val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry")

// 可修改
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")

// casting
val fruit: MutableSet<String> = mutableSetOf("apple", "banana", "cherry", "cherry")
val fruitLocked: Set<String> = fruit

// 访问元素
// 由于集合是无序的,因此无法访问特定索引处的元素。
1
2
3
4
5
6
7
8
9
10
11
12

# Map

地图将物品存储为键值对的形式。你可以通过键来访问对应的值

// 只读
val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(readOnlyJuiceMenu)
// {apple=100, kiwi=190, orange=100}

// 可修改
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
println(juiceMenu)
// {apple=100, kiwi=190, orange=100}

// casting
val juiceMenu: MutableMap<String, Int> = mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100)
val juiceMenuLocked: Map<String, Int> = juiceMenu

// 访问元素
readOnlyJuiceMenu["apple"]

// 添加元素
juiceMenu["coconut"] = 150 // Add key "coconut" with value 150 to the map
println(juiceMenu)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 控制流

如果你需要在 if 和 when 之间做出选择,我们建议使用 when

# if

有返回值

val d: Int
val check = true

if (check) {
    d = 1
} else {
    d = 2
}

println(d)
// 1
1
2
3
4
5
6
7
8
9
10
11

# When

当你的条件表达式包含多个分支时,可以使用 when 。类似switch

val obj = "Hello"

when (obj) {
    // 判断 obj 是否等于 "1"
    "1" -> println("One")

    // 判断 obj 是否等于 "Hello"
    "Hello" -> println("Greeting")

    // 默认分支:前面的条件都不满足时执行
    else -> println("Unknown")
}

// 接收参数
val obj = "Hello"    

    // 如果 obj 等于 "1",则 result 为 "One"
    "1" -> "One"

    // 如果 obj 等于 "Hello",则 result 为 "Greeting"
    "Hello" -> "Greeting"

    // 如果前面的条件都不满足,则 result 为 "Unknown"
    else -> "Unknown"
}
println(result)

// 表达式没有主语
fun main() {
    //  // 红绿灯当前状态,可以是 "Green"、"Yellow" 或 "Red"
    val trafficLightState = "Red"

    val trafficAction = when {
        trafficLightState == "Green" -> "Go"
        trafficLightState == "Yellow" -> "Slow down"
        trafficLightState == "Red" -> "Stop"
        else -> "Malfunction"
    }

    println(trafficAction)
    // Stop
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# Ranges

使用 .. 运算符

// 1..4 相当于 1, 2, 3, 4 


// 声明一个不包括末尾值的范围
// 1..<4 相当于 1, 2, 3

// 以相反的顺序
//  4 downTo 1 相当于 4, 3, 2, 1
1
2
3
4
5
6
7
8

# 循环

# for

用括号 () 括起来,并使用关键字 in 进行标识

for (number in 1..5) { 
    // number is the iterator and 1..5 is the range
    print(number)
}
// 12345

// 集合也可以
val cakes = listOf("carrot", "cheese", "chocolate")

for (cake in cakes) {
    println("Yummy, it's a $cake cake!")
}
// Yummy, it's a carrot cake!
// Yummy, it's a cheese cake!
// Yummy, it's a chocolate cake!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# While

while

当条件表达式为真时执行某个代码块

var cakesEaten = 0
while (cakesEaten < 3) {
    println("Eat a cake")
    cakesEaten++
}
// Eat a cake
// Eat a cake
// Eat a cake
1
2
3
4
5
6
7
8

do-while

先执行代码块,然后再检查条件表达式的结果

var cakesEaten = 0
var cakesBaked = 0
while (cakesEaten < 3) {
    println("Eat a cake")
    cakesEaten++
}
do {
    println("Bake a cake")
    cakesBaked++
} while (cakesBaked < cakesEaten)
// Eat a cake
// Eat a cake
// Eat a cake
// Bake a cake
// Bake a cake
// Bake a cake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 函数

fun sum(x: Int, y: Int): Int {
    return x + y
}

fun main() {
    println(sum(1, 2))
    // 3
}
1
2
3
4
5
6
7
8

# 参数

fun printMessageWithPrefix(message: String, prefix: String) {
    println("[$prefix] $message")
}

fun main() {
    // Uses named arguments with swapped parameter order
    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // [Log] Hello
}
1
2
3
4
5
6
7
8
9

# 默认参数

第一个被跳过的参数开始,后续的所有参数都需要加上名称。

fun printMessageWithPrefix(message: String, prefix: String = "Info") {
    println("[$prefix] $message")
}

fun main() {
    // Function called with both parameters
    printMessageWithPrefix("Hello", "Log") 
    // [Log] Hello
    
    // Function called only with message parameter
    printMessageWithPrefix("Hello")        
    // [Info] Hello
    
    printMessageWithPrefix(prefix = "Log", message = "Hello")
    // [Log] Hello
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 不返回值的函数

没有返回任何有用的值,那么其返回类型就是 Unit

fun printMessage(message: String) {
    println(message)
    // `return Unit` or `return` is optional
}

fun main() {
    printMessage("Hello")
    // Hello
}
1
2
3
4
5
6
7
8
9

# 单表达式函数

fun sum(x: Int, y: Int): Int {
    return x + y
}
fun sum(x: Int, y: Int) = x + y

fun main() {
    println(sum(1, 2))
    // 3
}

fun isLoggedIn(): Boolean = _currentUser.value.token.isNotEmpty()
1
2
3
4
5
6
7
8
9
10
11

# Lambda 表达式

没有名字的函数,也叫匿名函数。

{ 参数 ->
    执行代码
}

{ name: String -> 
    println(name)
}

// 也可以写成一行
val printName = { name: String -> println(name) }

// Compose
Button(
    onClick = {
        println("点击了按钮")
    }
) {
    Text("点击")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

格式

// 普通函数
fun uppercaseString(text: String): String {
    return text.uppercase()
}
fun main() {
    println(uppercaseString("hello"))
    // HELLO
}


// lambda 表达式
fun main() {
    val upperCaseString = { text: String -> text.uppercase() }
    println(upperCaseString("hello"))
    // HELLO
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

将 lambda 表达式传递给函数

// filter 
val numbers = listOf(1, -2, 3, -4, 5, -6)

val positives = numbers.filter ({ x -> x > 0 })

val isNegative = { x: Int -> x < 0 }
val negatives = numbers.filter(isNegative)

println(positives)
// [1, 3, 5]
println(negatives)
// [-2, -4, -6]

// map
// 对集合中的元素进行转换
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 }

val isTripled = { x: Int -> x * 3 }
val tripled = numbers.map(isTripled)

println(doubled)
// [2, -4, 6, -8, 10, -12]
println(tripled)
// [3, -6, 9, -12, 15, -18]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

例子

fun main() {
    calculate(num1 = 10, num2 = 5, operation = { a, b -> a + b })
    calculate(num1 = 10, num2 = 5, operation = { a, b -> a * b })
    calculate(num1 = 10, num2 = 5, operation = { a, b -> a - b })
    calculate(num1 = 10, num2 = 5, operation = { a, b -> a / b })
}

fun calculate(
    num1: Int,
    num2: Int,
    operation: (Int, Int) -> Int
): Int {
    return operation(num1, num2)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

it 关键字

val fruits = listOf("苹果", "香蕉", "橘子")

// 完整写法
fruits.filter { fruit -> fruit.length == 2 }

// 简化(用 it,it 自动代表每个元素)
fruits.filter { it.length == 2 }
1
2
3
4
5
6
7

# 面向对象

# 定义

// 在类名后面加上括号中的 ()
class Contact(val id: Int, var email: String) {
    // 在由大括号{}定义的类体内部
    val category: String = ""
}
1
2
3
4
5

建议将属性声明为只读属性( val ),除非在创建类的实例之后还需要对这些属性进行修改

类属性也可以拥有默认值

class Contact(val id: Int, var email: String = "example@gmail.com") {
    val category: String = "work"
}
1
2
3

# 创建实例

使用构造函数来声明一个类实例

class Contact(val id: Int, var email: String)

fun main() {
    // contact 是 Contact 类的实例,id 和 email 是属性。
    val contact = Contact(1, "mary@gmail.com")
}
1
2
3
4
5
6

# 访问属性

实例名称后.该属性的名称

class Contact(val id: Int, var email: String)

fun main() {
    val contact = Contact(1, "mary@gmail.com")

    println(contact.email)           
    // mary@gmail.com
    
    // 使用字符串模板
    println("Their email address is: ${contact.email}")
}

1
2
3
4
5
6
7
8
9
10
11
12

# 成员函数

通过成员函数来定义对象的的行为

class Contact(val id: Int, var email: String) {
    fun printId() {
        println(id)
    }
}

fun main() {
    val contact = Contact(1, "mary@gmail.com")
    // Calls member function printId()
    contact.printId()           
    // 1
}
1
2
3
4
5
6
7
8
9
10
11
12

# init 初始化块

在创建对象时,执行初始化逻辑。

调用主构造函数
↓
初始化属性
↓
执行 init
↓
对象创建完成
1
2
3
4
5
6
7

初始化逻辑

class User(
    val name: String
) {

    init {
        println("初始化用户:$name")
    }
}
1
2
3
4
5
6
7
8

参数校验

class User(
    val name: String,
    val age: Int
) {

    init {
        require(age >= 0) {
            "年龄不能小于 0"
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 主构造函数和次构造函数

主构造函数是类最主要的创建方式,次构造函数是额外补充的创建方式。

先完成主构造函数和 init,再执行次构造函数自己的代码。可以有多个次构造函数。

// 主构造函数
class User(
    val name: String,
    val age: Int
)

val user = User("张三", 25)

// 次构造函数
// 如果一个类有主构造函数,那么次构造函数必须保证这些核心属性最终都有值。
class User(
    val name: String,
    val age: Int
) {

    constructor(name: String) : this(name, 0)
}
// 现在有两种方式创建对象:
val user1 = User("张三", 25)
val user2 = User("李四")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Kotlin 中很多时候不需要次构造函数

因为 Kotlin 支持默认参数。

class User(
    val name: String,
    val age: Int
) {

    constructor(name: String) : this(name, 0)
}

// 默认参数
class User(
    val name: String,
    val age: Int = 0
)
1
2
3
4
5
6
7
8
9
10
11
12
13

执行顺序

1. 先进入次构造函数
   constructor(name: String)

2. 次构造函数通过
   : this(name, 0)
   调用主构造函数

3. 主构造函数完成参数和属性初始化

4. 执行 init 初始化块

5. 回到次构造函数

6. 执行次构造函数 { } 里的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 继承

Kotlin 默认所有类都是 final 的(不可继承),必须使用 open 修饰符使类可继承,方法也必须标记为 open 才能被重写。

/**
 * 动物类
 */
open class Animal(var name: String) {

    fun makeSound() {
        println("$name 正在发出声音")
    }
}

/**
 * 狗类,继承动物,产生狗对象
 */
class Dog(name: String) : Animal(name) {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

子类重写方法必须使用 override 修饰符,子类必须调用父类的主构造函数;如果父类没有主构造函数,子类必须显式调用父类的次构造函数。

/**
 * 狗类,继承动物,产生狗对象
 */
class Dog(name: String) : Animal(name) {

    // 重写父类的方法
    override fun makeSound() {

        // 调用父类的方法
        super.makeSound()

        println("$name 正在汪汪汪大叫")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 接口Interface

Kotlin 中的接口和 Java 类似:

  • 接口只负责声明一个类必须拥有哪些能力(方法)或属性。
  • 不负责实现这些能力的具体细节。
// 定义接口
interface Swimmable {

    // 声明游泳能力,不写具体实现
    fun swim()
}

// 实现接口
class Dog(val name: String) : Swimmable {

    // 实现接口中的方法
    override fun swim() {
        println("$name 正在游泳")
    }
}

// 使用
fun main() {

    val dog = Dog("旺财")

    dog.swim()
}

// 输出
旺财 正在游泳

// 关系流程
Swimmable 接口
    ↓
声明:必须具有 swim() 能力
    ↓
Dog 实现 Swimmable
    ↓
必须实现 swim()
    ↓
dog.swim()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

实现多个接口

interface Swimmable {
    fun swim()
}

interface Runnable {
    fun run()
}

class Dog(val name: String) : Swimmable, Runnable {
    override fun swim() {
        println("$name 正在游泳")
    }

    override fun run() {
        println("$name 正在奔跑")
    }
}

val dog = Dog("旺财")

dog.swim()
dog.run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 数据类data class

data class User(val name: String, val id: Int)
1

在生成成员函数时,Kotlin 编译器只会使用主构造函数中定义的属性。如果你在数据类的主体中声明了属性,这些属性不会包含在生成的函数的输出结果中

数据类中最实用的预定义成员函数包括

# 自动帮你生成的常用方法

toString()

输出一个易于阅读的字符串,其中包含类实例及其属性信息。

val user = User("Alex", 1)

println(user)            
// User(name=Alex, id=1)
1
2
3
4

equals() or ==

比较两个对象的数据是否相同

val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)

// Compares user to second user
println("user == secondUser: ${user == secondUser}") 
// user == secondUser: true

// Compares user to third user
println("user == thirdUser: ${user == thirdUser}")   
// user == thirdUser: false
1
2
3
4
5
6
7
8
9
10
11

copy()

通过复制另一个实例来创建新的类实例,可能会有一些不同的属性。

创建实例的副本比直接修改原始实例更为安全,因为那些依赖原始实例的代码不会受到副本的影响,而你对副本所做的操作也不会受到影响。

val user = User("Alex", 1)

// Creates an exact copy of user
println(user.copy())       
// User(name=Alex, id=1)

// Creates a copy of user with name: "Max"
println(user.copy("Max"))  
// User(name=Max, id=1)

// Creates a copy of user with id: 3
println(user.copy(id = 3)) 
// User(name=Alex, id=3)
1
2
3
4
5
6
7
8
9
10
11
12
13

# object单例

object 经常用于 Manager、Utils、Config 等需要全局共享的对象。

网络客户端和登录管理器全局只需要一个实例,用单例确保不会重复创建。

object BluetoothManager {

    var isConnected: Boolean = false

    fun connect() {
        isConnected = true
        println("蓝牙连接成功")
    }

    fun disconnect() {
        isConnected = false
        println("蓝牙已断开")
    }
}

// 使用
fun main() {

    BluetoothManager.connect()

    println(BluetoothManager.isConnected)

    BluetoothManager.disconnect()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# companion object 伴生对象

定义在类内部、与这个类关联的单例对象。

class 类名 {

    companion object {

        // 属性

        // 方法
    }
}

class User {

    companion object {

        val type = "普通用户"

        fun create() {
            println("创建用户")
        }
    }
}

// 使用
println(User.type)

User.create()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

普通成员属于对象实例。

companion object不属于某一个具体对象,而是属于整个 User 类。

object 和 companion object 区别

object companion object
含义 独立单例对象 类内部的伴生对象
位置 可以独立定义 必须写在类里面
调用 AuthManager.login() User.create()
实例数量 一个 每个类最多一个伴生对象
常见用途 Manager、Utils、Config 常量、工厂方法、类级别方法

const val 和 val 的区别

class AppConfig {

    companion object {

        // const 常量:编译期就确定,必须是确定的值
        // val 只读变量:运行时才确定,可以是确定的值,也可以是一个表达式

        const val BASE_URL = ""
        const val PAGE_SIZE = 10
        const val TIME_OUT = 1000
    }
}

const val
→ 编译期确定
→ 常量
→ 不能修改

val
→ 可以运行时确定
→ 只读变量
→ 确定之后不能重新赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# enum枚举

一组数量固定、提前确定好的值。

普通 class 用来创建各种对象;enum class 用来规定“只能从这几个固定值里选一个”。

enum class TrafficLight {
    RED,
    YELLOW,
    GREEN
}

enum class BluetoothStatus {
    DISCONNECTED,
    CONNECTING,
    CONNECTED
}
1
2
3
4
5
6
7
8
9
10
11

常见搭配

val status = BluetoothStatus.CONNECTED

when (status) {

    BluetoothStatus.DISCONNECTED -> {
        println("设备未连接")
    }

    BluetoothStatus.CONNECTING -> {
        println("设备连接中")
    }

    BluetoothStatus.CONNECTED -> {
        println("设备已连接")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

枚举可以有多个属性

enum class BluetoothStatus(
    val code: Int,
    val description: String
) {
    DISCONNECTED(0, "未连接"),
    CONNECTING(1, "连接中"),
    CONNECTED(2, "已连接")
}

// 使用
val status = BluetoothStatus.CONNECTED

println(status.code)
println(status.description)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

每个枚举值默认都有一些属性

enum class Status {
    LOADING,
    SUCCESS,
    ERROR
}

// 获取枚举名称 name
println(Status.SUCCESS.name)

// 获取枚举值的位置 ordinal
// 一般不要把 ordinal 当业务状态码,因为调整枚举顺序后它就会改变
println(Status.LOADING.ordinal)

// 获取所有枚举值
for (status in Status.entries) {
    println(status)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 空安全

Kotlin 支持可空类型,这意味着所声明的类型可以包含 null 值。默认情况下,类型是不允许接受 null 值的。可空类型是通过在类型声明后添加 ? 来定义的。

fun main() {
    // neverNull has String type
    var neverNull: String = "This can't be null"

    // Throws a compiler error
    neverNull = null

    // nullable has nullable String type
    var nullable: String? = "You can keep a null here"

    // This is OK
    nullable = null

    // By default, null values aren't accepted
    var inferredNonNull = "The compiler assumes non-nullable"

    // Throws a compiler error
    inferredNonNull = null

    // notNull doesn't accept null values
    fun strLength(notNull: String): Int {                 
        return notNull.length
    }

    println(strLength(neverNull)) // 18
    println(strLength(nullable))  // Throws a compiler error
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 检查是否存在空值

// 判断 maybeString 是否不等于 null ,以及其 length 是否大于零
fun describeString(maybeString: String?): String {
    if (maybeString != null && maybeString.length > 0) {
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"
    }
}

fun main() {
    val nullString: String? = null
    println(describeString(nullString))
    // Empty or null string
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 使用安全调用函数

使用安全调用运算符 ?.

fun lengthString(maybeString: String?): Int? = maybeString?.length

fun main() { 
    val nullString: String? = null
    println(lengthString(nullString))
    // null
}
1
2
3
4
5
6
7

可以进行链式操作 person.company?.address?.country

# 使用 Elvis 操作符

如果你使用 Elvis 运算符 ?: 来检测到了 null 的值,你可以提供一个默认值来作为返回值。

fun main() {
    val nullString: String? = null
    println(nullString?.length ?: 0)
    // 0
}
1
2
3
4
5

# !! 非空断言

val name: String? = "张三"

val value = name!! // “我确定这个变量绝对不是 null,你不用检查。”
1
2
3

# ?.let { }

name?.let {
    println(it)
}

// name 不为 null,才执行 { } 中的代码。


val name: String? = "张三"

name?.let {
    println("用户名:$it")
    println("名字长度:${it.length}")
}

// 输出
用户名:张三
名字长度:2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
写法 含义 null 时
String? 允许为空 可以保存 null
?. 安全调用 返回 null
?: 提供默认值 使用右边的值
!! 强制非空 抛出异常
?.let {} 非空才执行代码 整个代码块不执行
// 允许为空
val name: String? = null

// 安全访问
name?.length

// 空时给默认值
name ?: "匿名"

// 安全访问 + 默认值
name?.length ?: 0

// 非空才执行
name?.let {
    println(it)
}

// 强制非空(谨慎使用)
name!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 扩展函数

不修改原来的类,也可以给这个类“增加”一个可以像成员函数一样调用的方法。

val name = "zhangsan"

// String 是 Kotlin 提供的类,我们又不想修改它的源码
fun String.printName() {
    println(this)
}

val name = "张三"

name.printName()
1
2
3
4
5
6
7
8
9
10

执行流程

fun
↓
定义函数

String.
↓
我要给 String 扩展功能

printName()
↓
扩展的方法
1
2
3
4
5
6
7
8
9
10
11

# 扩展属性

面试通中 Context.showToast(msg) 就是给 Context 添加扩展函数,简化 Toast 调用。

Compose 的 .dp、.sp 也是扩展属性,给数字添加单位。

// 给 String 添加扩展属性
val String.isLong: Boolean
    get() = this.length > 10

// 使用
val text = "这是一个很长的字符串"
println(text.isLong)  // true
1
2
3
4
5
6
7

# 作用域函数

函数 对象怎么写 内部对象 返回值 常见用途
let obj.let it 最后一行 空安全、转换
with with(obj) this 最后一行 集中操作对象
run obj.run this 最后一行 操作 + 计算
apply obj.apply this 对象本身 初始化、配置
also obj.also it 对象本身 日志、附加操作

let:使用 it 操作对象

对象.let {
    // it 就是这个对象
}
1
2
3

最常见:配合空安全

val name: String? = "张三"

name?.let {
    println("用户名:$it")
    println("长度:${it.length}")
}
1
2
3
4
5
6

with:使用 this 集中操作对象

with(对象) {
    // this 就是这个对象
}
1
2
3

例子

// 传统写法
car.startEngine()
car.turnOnAC()
car.playMusic("Jazz")
car.setNavigation("Home")

// 使用with 只不过 this 可以省略 
with(car) {
    startEngine()
    turnOnAC()
    playMusic("Jazz")
    setNavigation("Home")
}
1
2
3
4
5
6
7
8
9
10
11
12
13

run:操作对象并计算结果

对象.run {
    // this
}

val user = User("张三", 25)

val result = user.run {
    println(name)
    "$name,今年$age岁"
}

println(result)
1
2
3
4
5
6
7
8
9
10
11
12

apply:配置、初始化对象

特别适合创建对象后立即配置对象

对象.apply {
    // this
}
val user = User("", 0).apply {
    name = "张三"
    age = 25
}
1
2
3
4
5
6
7

also:顺便做一件事

对象.also {
    // it
}

val user = User("张三", 25).also {
    println("创建用户:${it.name}")
}
1
2
3
4
5
6
7
对象可能为 null?
→ ?.let { }

需要初始化 / 配置对象?
→ apply { }

操作对象并得到一个结果?
→ run { }

需要对同一个对象连续做很多操作?
→ with(obj) { }

只是顺便打印日志等操作?
→ also { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 协程

协程是运行在线程上的轻量级任务

处理耗时、异步、并发任务,同时避免阻塞当前线程

lifecycleScope.launch { } → 创建协程作用域

launch { } → 在作用域中启动协程

两个协程并发执行,不是等一个执行完了再执行另一个

协程 1 先启动但后完成(delay 更久),协程 2 后启动但先完成

​

# suspend 挂起函数

这个函数可以暂停执行,等结果回来后恢复执行

suspend fun getUser(): String {
    delay(1000)
    return "张三"
}
1
2
3
4

流程

协程执行
↓
遇到 delay
↓
当前协程挂起
↓
线程可以执行其他任务
↓
3 秒后
↓
协程继续执行
1
2
3
4
5
6
7
8
9
10
11

suspend 函数只能在协程中或其他 suspend 函数中调用。在普通函数中直接调用会报错。

interface ApiService {
    @POST("login")
    suspend fun login(@Body params: LoginParams): ApiResponse<User>
}
1
2
3
4

# launch 和 async 区别

launch async
作用 启动任务 启动有结果的任务
返回 Job Deferred<T>
获取结果 不需要 await()
常见场景 上传、保存、网络请求、更新UI 请求数据、计算、多个请求并发、等结果

# 协程并发执行

suspend fun getUser(): String {
    delay(2000)
    return "用户"
}

suspend fun getOrder(): String {
    delay(2000)
    return "订单"
}

// 按顺序执行
val user = getUser()
val order = getOrder()

// 消耗时间
getUser  2秒
   ↓
getOrder 2秒

总计约 4 秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

如果两个请求互相没有依赖

val user = async {
    getUser()
}

val order = async {
    getOrder()
}

println(user.await())
println(order.await())

// 执行时间
getUser  ────── 2秒 ──────→

getOrder ────── 2秒 ──────→

总计约 2 秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

多个协程可以并发执行,用 coroutineScope 等待所有子协程完成:

suspend fun loadAllData() = coroutineScope {
    // 任务1:耗时1秒
    launch {
        delay(1000)
        println("任务1完成")
    }

    // 任务2:耗时0.5秒
    launch {
        delay(500)
        println("任务2完成")
    }

    // 任务3:用async拿到结果
    val result = async {
        delay(800)
        "任务3结果"
    }.await()
    println("任务3: $result")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  1. coroutineScope 是挂起函数,会阻塞当前协程,直到内部所有 launch / async 子协程全部执行完毕才返回;
  2. 三个任务并发跑,总耗时≈最长任务 1000ms,不是 1000+500+800 串行累加;
  3. async 配合 await() 用于需要返回结果的并发任务;launch 适合不需要返回值的任务。

# Dispatchers 调度器

协程可以指定在哪类线程上执行。

调度器 对应线程 适用场景
Dispatchers.Main Android 主线程 UI 更新
shturl.cc/F2qH IO 线程 网络、数据库、文件操作
Dispatchers.Default 默认线程池 CPU 计算任务
Dispatchers.Unconfined 无固定线程 较少直接使用
lifecycleScope.launch {
    println("当前线程: ${Thread.currentThread().name}")

    // 切换到 IO 线程(网络请求、文件操作)
    withContext(Dispatchers.IO) {
        println("IO线程: ${Thread.currentThread().name}")
        delay(500)
        println("IO操作完成")
    }

    // 切换到 Default 线程(CPU密集型计算)
    withContext(Dispatchers.Default) {
        println("Default线程: ${Thread.currentThread().name}")
        val list = (1..1000).sortedDescending()
        println("排序完成,最大值: ${list.first()}")
    }

    // 回到原来的线程
    println("回到线程: ${Thread.currentThread().name}")
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Retrofit 的 suspend 函数会自动切换到 IO 线程,不需要手动 withContext(Dispatchers.IO)。

viewModelScope.launch 默认在 Main 线程。withContext 主要用于需要精细控制线程的场景。

launch 和 withContext 比较

launch(Dispatchers.IO) {
    // 开启一个新的协程
}

withContext(Dispatchers.IO) {
    // 当前协程切换执行上下文
}
1
2
3
4
5
6
7

# 取消协程

val job = launch {
    repeat(100) {
        delay(1000)
        println(it)
    }
}

// 取消
job.cancel() // 发出取消请求
job.join() // 等待这个协程真正结束
job.cancelAndJoin() // cancel+join 简写
1
2
3
4
5
6
7
8
9
10
11

协程取消是“协作式”的

需要运行到可以响应取消的位置

# 手动判断协程是否还有效

while (isActive) {
    // 工作
}

val job = launch {

    while (isActive) {
        println("正在执行")
    }
}

job.cancel()

isActive 会变成 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 协程作用域汇总

协程作用域 生命周期 使用位置 典型场景 重要特点
viewModelScope ViewModel存活期间 ViewModel内部 网络请求、数据处理、Flow 发射事件 ViewModel销毁自动取消;屏幕旋转不会销毁,正式项目最常用
rememberCoroutineScope Composable 组件存活期间 Compose 函数内 按钮点击等 UI 交互中启动协程 组件销毁自动取消;在 onClick等回调里调用 launch
LaunchedEffect 由传入 key 控制,key 消失则结束 Compose 函数内 页面进入自动加载、收集 Flow/SharedFlow事件 自动创建协程;key 变化会取消旧协程,重新执行
lifecycleScope Activity / Fragment 存活期间 Activity / Fragment 页面级别的异步任务 页面销毁自动取消;传统 View 体系常用
runBlocking 阻塞直到内部协程全部执行完毕 main 函数、单元测试 练习、测试代码 阻塞当前线程,Android 业务代码禁止使用

速记

  • viewModelScope:ViewModel业务异步(网络请求首选)
  • rememberCoroutineScope:Compose点击事件里开协程
  • LaunchedEffect:页面自动执行、收集 Flow 事件
  • lifecycleScope:Activity/Fragment页面协程
  • runBlocking:仅测试,阻塞线程,项目不用
上次更新: 2026/10/07, 07:27:34
最近更新
01
02
数据持久化
10-06
03
网络请求
10-04
更多文章>
Theme by Vdoing | Copyright © 2021-2026 WeiXiaojing | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式