Kotlin入门
# 基本格式
fun main(){
}
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})" → 题目信息拼接
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
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()
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
)
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
// 访问元素
// 由于集合是无序的,因此无法访问特定索引处的元素。
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)
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
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
}
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
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!
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
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
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
}
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
}
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
}
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
}
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()
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("点击")
}
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
}
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]
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)
}
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 }
2
3
4
5
6
7
# 面向对象
# 定义
// 在类名后面加上括号中的 ()
class Contact(val id: Int, var email: String) {
// 在由大括号{}定义的类体内部
val category: String = ""
}
2
3
4
5
建议将属性声明为只读属性(
val),除非在创建类的实例之后还需要对这些属性进行修改
类属性也可以拥有默认值
class Contact(val id: Int, var email: String = "example@gmail.com") {
val category: String = "work"
}
2
3
# 创建实例
使用构造函数来声明一个类实例
class Contact(val id: Int, var email: String)
fun main() {
// contact 是 Contact 类的实例,id 和 email 是属性。
val contact = Contact(1, "mary@gmail.com")
}
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}")
}
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
}
2
3
4
5
6
7
8
9
10
11
12
# init 初始化块
在创建对象时,执行初始化逻辑。
调用主构造函数
↓
初始化属性
↓
执行 init
↓
对象创建完成
2
3
4
5
6
7
初始化逻辑
class User(
val name: String
) {
init {
println("初始化用户:$name")
}
}
2
3
4
5
6
7
8
参数校验
class User(
val name: String,
val age: Int
) {
init {
require(age >= 0) {
"年龄不能小于 0"
}
}
}
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("李四")
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
)
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. 执行次构造函数 { } 里的代码
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) {
}
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 正在汪汪汪大叫")
}
}
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()
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()
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)
在生成成员函数时,Kotlin 编译器只会使用主构造函数中定义的属性。如果你在数据类的主体中声明了属性,这些属性不会包含在生成的函数的输出结果中
数据类中最实用的预定义成员函数包括
# 自动帮你生成的常用方法
toString()
输出一个易于阅读的字符串,其中包含类实例及其属性信息。
val user = User("Alex", 1)
println(user)
// User(name=Alex, id=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
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)
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()
}
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()
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
→ 可以运行时确定
→ 只读变量
→ 确定之后不能重新赋值
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
}
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("设备已连接")
}
}
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)
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)
}
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
}
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
}
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
}
2
3
4
5
6
7
可以进行链式操作
person.company?.address?.country
# 使用 Elvis 操作符
如果你使用 Elvis 运算符 ?: 来检测到了 null 的值,你可以提供一个默认值来作为返回值。
fun main() {
val nullString: String? = null
println(nullString?.length ?: 0)
// 0
}
2
3
4
5
# !! 非空断言
val name: String? = "张三"
val value = name!! // “我确定这个变量绝对不是 null,你不用检查。”
2
3
# ?.let { }
name?.let {
println(it)
}
// name 不为 null,才执行 { } 中的代码。
val name: String? = "张三"
name?.let {
println("用户名:$it")
println("名字长度:${it.length}")
}
// 输出
用户名:张三
名字长度:2
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!!
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()
2
3
4
5
6
7
8
9
10
执行流程
fun
↓
定义函数
String.
↓
我要给 String 扩展功能
printName()
↓
扩展的方法
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
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 就是这个对象
}
2
3
最常见:配合空安全
val name: String? = "张三"
name?.let {
println("用户名:$it")
println("长度:${it.length}")
}
2
3
4
5
6
with:使用 this 集中操作对象
with(对象) {
// this 就是这个对象
}
2
3
例子
// 传统写法
car.startEngine()
car.turnOnAC()
car.playMusic("Jazz")
car.setNavigation("Home")
// 使用with 只不过 this 可以省略
with(car) {
startEngine()
turnOnAC()
playMusic("Jazz")
setNavigation("Home")
}
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)
2
3
4
5
6
7
8
9
10
11
12
apply:配置、初始化对象
特别适合创建对象后立即配置对象
对象.apply {
// this
}
val user = User("", 0).apply {
name = "张三"
age = 25
}
2
3
4
5
6
7
also:顺便做一件事
对象.also {
// it
}
val user = User("张三", 25).also {
println("创建用户:${it.name}")
}
2
3
4
5
6
7
对象可能为 null?
→ ?.let { }
需要初始化 / 配置对象?
→ apply { }
操作对象并得到一个结果?
→ run { }
需要对同一个对象连续做很多操作?
→ with(obj) { }
只是顺便打印日志等操作?
→ also { }
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 "张三"
}
2
3
4
流程
协程执行
↓
遇到 delay
↓
当前协程挂起
↓
线程可以执行其他任务
↓
3 秒后
↓
协程继续执行
2
3
4
5
6
7
8
9
10
11
suspend 函数只能在协程中或其他 suspend 函数中调用。在普通函数中直接调用会报错。
interface ApiService {
@POST("login")
suspend fun login(@Body params: LoginParams): ApiResponse<User>
}
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 秒
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 秒
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")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
coroutineScope是挂起函数,会阻塞当前协程,直到内部所有launch/async子协程全部执行完毕才返回;- 三个任务并发跑,总耗时≈最长任务 1000ms,不是 1000+500+800 串行累加;
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}")
}
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) {
// 当前协程切换执行上下文
}
2
3
4
5
6
7
# 取消协程
val job = launch {
repeat(100) {
delay(1000)
println(it)
}
}
// 取消
job.cancel() // 发出取消请求
job.join() // 等待这个协程真正结束
job.cancelAndJoin() // cancel+join 简写
2
3
4
5
6
7
8
9
10
11
协程取消是“协作式”的
需要运行到可以响应取消的位置
# 手动判断协程是否还有效
while (isActive) {
// 工作
}
val job = launch {
while (isActive) {
println("正在执行")
}
}
job.cancel()
isActive 会变成 false
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:仅测试,阻塞线程,项目不用