编译型语言:源代码一次性地编译成计算机可识别的二进制文件,然后计算机直接执行。
解释型语言:解释器会一行行地读取我们编写的源代码,然后实时地将这些源代码解释成计算机可识别的二进制数据后再执行。JAVA 编译程式 class 文件,通过 Java虚拟机 担任解释器。
基本语法#
- val 不可变,var 可变 基于类型推导判断数据类型。对于延迟初始化时则需要显式声明。
- Kotlin 完全抛弃了Java中的 基本数据类型,全部使用了对象数据类型(引用类)。
** 永远优先使用val来声明一个变量,而当val没有办法满足你的需求时再使用var。这样设计出来的程 序会更加健壮,也更加符合高质量的编码规范 **
- When 语句
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
} - 区间
val range = 0..10 // 双端闭区间
val range = 0 until 10 // 左闭右开区间面向对象#
**任何一个非抽象类默认都是不可以被继承的(与 Java 不同) **,如果一个类不是专门为继承而设计的,就应该主动加上 final 声明,对于可继承的类需要添加 open 关键词。
Kotlin将构造函数分 成了两种:主构造函数和次构造函数。 每个类默认都会有一个不带参数的主构造函数,。 所有主构造函数中的逻辑 ,在 init 中实现。
继承#
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
...
} 每个类默认会有一个主构造函数,默认为不带参的。
任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可 以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。
**子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。 **
** 在主构造函数中声明为 ****val****或 ****var****的参数将自动成为该类的属性(字段)。没有 ****val****或 ****var**前缀的参数只是构造函数参数,不会成为该类的属性。
open class Person(val name: String, val age: Int)
class Student(
val sno: String,
val grade: Int,
val studentName: String, // Student自己的属性
val studentAge: Int // Student自己的属性
) : Person(studentName, studentAge) {
fun showInfo1() {
// 访问继承的属性
println("继承的属性:")
println("姓名: $name") // 继承自Person
println("年龄: $age") // 继承自Person
}
fun showInfo2() {
// 访问自己的属性
println("自己的属性:")
println("学生姓名: $studentName")
println("学生年龄: $studentAge")
}
fun compareNames() {
println("继承的name: $name")
println("自己的studentName: $studentName")
println("是否相等: ${name == studentName}") // 应该是true
}
}大多数情况下需要加入 super 关键字。除非如上,子类给父类 构造器 传参。
当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造 函数(包括间接调用)。
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age) {
}
constructor() : this("", 0) { // 调用了第一个次构造函数
}
}接口#
无论是继承还是接口, Kotlin中统一使用冒号,中间用逗号进行分隔。
可见性修饰符#

#

数据类与单例类#
当在一个类前 面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成, 从而大大减少了开发的工作量。
在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可。

Lambda 编程#
listOf()函数创建的是一个不可变的列表。你也许不太能理解什么叫作不可变的列表。 使用 mutableListOf() 创建是可变的列表。
对应的还有以下函数
setOf()
mutableSetOf()
mapOf()
mutableMapOf()**集合的函数式API **
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val maxLengthFruit = list.maxBy { it.length }
val lambda = { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy(lambda)
// 可简化为
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
val maxLengthFruit = list.maxBy { it.length } Lambda表达式
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体} 集合中的map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定。
- filter函数:则是根据条件,过滤参数。
- any函数:用于判断集 合中是否至少存在一个元素满足指定条件
- all函数:用于判断集合中是否所有元素都满足指定条件
**如果我们在Kotlin代码中调用了一个 Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。**Java单抽象 方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
v.visibility = View.GONE
}
});
// 转为Kotlin写法 并不断简化写法
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
v?.visibility = View.GONE
}
});
button.setOnClickListener(View.OnClickListener {
v: View? -> v?.visibility = View.GONE
});
button.setOnClickListener {
v: View? -> v?.visibility = View.GONE
}
button.setOnClickListener {
v -> v?.visibility = View.GONE
}
button.setOnClickListener {
it?.visibility = View.GONE
}空指针检查#
if (a != null) {
a.doSomething()
}
// 简化
a?.doSomething()
val c = if (a ! = null) {
a
} else {
b
}
// 简化
val c = a ?: b
// 非空断言
a!!.doSomething() let 函数
这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到 Lambda表达式中。
fun doStudy(study: Study?) {
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
} 字符串内嵌表达式
"hello, ${obj.name}. nice to meet you!"
// 仅有一个变量时
"hello, $name. nice to meet you!"函数参数默认值(构造函数也可以)
fun printParams(num: Int = 100, str: String) {
println("num is $num , str is $str")
} 标准函数#
with#
with函数接收两个参数:第一个参数可以是一个任意类型的对 象,第二个参数是一个Lambda表达式。Lambda表达式的最后一行代码会作为with函数的返回值返回。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = with(StringBuilder()) {
// 省去了反复调用 builder 的语句
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
println(result) run#
run函数通常不会直接调用, 而是要在某个对象的基础上调用;其次r**un函数只接收一个Lambda参数,并且会在Lambda表 达式中提供调用对象的上下文。 **
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
println(result) apply#
是apply函数无法指定返回值,而是会自动返回调用对象本身。
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
// toString()方法写在了下边
}
println(result.toString()) 静态方法#
Kotlin提供了比静态方法更好用的语法特性,即单例类。
使类中的某一个方法变成静态方法的调用方式:companion object 伴生类
真正的静态方法: 注解和顶层方法
- 注解:半生类方法加上@JvmStatic注解
- 顶层方法: 指的是那些没有定义在任何类中的方法, Kotlin编译器会将所有的顶层方法全部编译成静态方法,因只要你定义了一 个顶层方法,那么它就一定是静态方法。 (直接不在类中定义)
延迟初始化#
class MainActivity : AppCompatActivity(), View.OnClickListener {
private var adapter: MsgAdapter? = null // 全局定义
override fun onCreate(savedInstanceState: Bundle?) {
...
// onCreate生命周期中初始化
adapter = MsgAdapter(msgList)
...
}
}
override fun onClick(v: View?) {
...
// 多处需要判空
adapter?.notifyItemInserted(msgList.size - 1)
...
}延迟初始化 - (有可能产生空指针问题)
private lateinit var adapter: MsgAdapter在adapter变量的前面加上了lateinit关键字,这样就不用在一开始的时候 将它赋值为null 。 它可以告诉Kotlin编译器, 该变量会在晚些时候进行初始化。
密封类#
sealed class
sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()
fun getResultMsg(result: Result) = when (result) {
is Success -> result.msg
is Failure -> "Error is ${result.error.message}"
} 当在when语句中传入一个密封类变量 作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应 的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的 情况。
扩展函数#
扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打 开这个类,向该类添加新的函数。
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
} 运算符重载#
class Obj {
operator fun plus(obj: Obj): Obj {
//
处理相加的逻辑
}
} 
高阶函数#
**如果一个函数接收另一个函数作为参数,或者返回值的类型是 另一个函数,那么该函数就称为高阶函数。 **
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
// 使用 Lambda 表达式,省去定义类型相匹配的函数
val result2 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
} // build 方法接收 一个函数
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
// 调用build函数时传入的Lambda表达式将会自动拥有StringBuilder的上下文
block()
return this
}内联函数#
高阶函数转换为 Java 的代码
public static int num1AndNum2(int num1, int num2, Function operation) {
int result = (int) operation.invoke(num1, num2);
return result;
}
public static void main() {
int num1 = 100;
int num2 = 80;
int result = num1AndNum2(num1, num2, new Function() {
@Override
public Integer invoke(Integer n1, Integer n2) {
return n1 + n2;
}
});
} 实际: Lambda表达式在底层被转换成了匿名类的实现方式。这就表明,我们每调用一次Lambda表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销 。通过使用 inline关键字 可以避免此问题。
内联函数函数体复制到调用处,避免创建额外的Function对象,从而提高性能。内联函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数属性。非内联的函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,这也是它最大的局限性。
**内联函数所引用的Lambda表达中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回。 **
- 非内联函数的 Lambda:
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">return</font>只能返回Lambda 自身(局部返回),想返外部函数要写<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">return@函数名</font>;- 内联函数的 Lambda:
<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">return</font>直接返回调用 Lambda 的外部函数(函数返回),这是内联的专属特性(也叫非局部返回)。
inline fun runRunnable(block: () -> Unit) {
// 为内联函数的Lambda表达式中允许使用return关键字,
// 和高阶函数的匿名类实现中不允许使用return关键字之间造成了冲突。
val runnable = Runnable {
block()
}
runnable.run()
} ** 在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现 中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误。此时需要使用 crossline 关键字**
noinline#
一个高阶函数中如果接收了两个或者更多函数类型的参数,这时我们给函数加上了inline关键字,那么Kotlin编译器会自动将所有引用的 Lambda表达式全部进行内联 。使用 noinline 来内联其中的一个Lambda表达式 。
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) { } - ** 内联的函数类型参数只允许传递给另外一个内联函数 **
// 内联函数
inline fun process(action: () -> Unit) {
println("开始处理")
action() // 这里会内联展开
println("结束处理")
}
// 普通函数
fun normalFunction(action: () -> Unit) {
println("普通函数")
action() // 这里不会内联
}
inline fun highOrderInline(action: () -> Unit) {
// 如果将 action 传递给 normalFunction
normalFunction(action) // ❌ 编译错误!
// 原因:action 在编译时需要被内联展开,
// 但 normalFunction 的实现是未知的(可能在另一个模块中)
// 编译器无法确定如何展开
}- ** 内联函数所引用的Lambda表达式中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回**
fun printString(str: String, block: (String) -> Unit) {
println("printString begin")
block(str)
println("printString end")
}
fun main() {
println("main start")
val str = ""
printString(str) { s ->
println("lambda start")
if (s.isEmpty()) return@printString // 局部返回,若需要全局返回则需要添加 inline 关键字
println(s)
println("lambda end")
}
println("main end")
} crossinline#
如果我们在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现中调用函数类型参数时,再将高阶函数声明成内联函数,就一定会提示错误。 内联函数的Lambda表达式中允许使用return关键字,和高阶函数的匿名类实现中不允许使用return关键字之间造成了冲突。
inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
} 而crossinline关键字 用于保证在内联函数的Lambda表达式中一定不会使用return关键字。 所以上述代码要修改为inline fun runRunnable(crossinline block: () -> Unit)
泛型和委托#
泛型#
如果想要让泛型的类型不可为空,需要将泛型的上界手动指定成Any
泛型进行实化#
- 函数必须是内联函数
- 声明泛型的地方必须加上reified关键字来表示该泛型要进行实化。
Java的泛型功能是通过类型擦除机制来实现的,编译时进行类型检查,运行时被擦除。
泛型对于类型的约束只在编译时期存在, JVM是识别不出来代码中指定的泛型类型的。
// 无法获取泛型的实际类型
public <T> void printType(T obj) {
// 这里无法获取T的具体类型
Class<T> clazz = T.class; // 编译错误!
// 只能获取运行时类型
Class<?> runtimeClass = obj.getClass(); // 可行,但这不是泛型类型
}Kotlin 内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样也就不存在泛型擦除问题了
inline fun <reified T> getGenericType() = T::class.java 通过内联方式将泛型进行实化。 同时使用 inline和**reified关键字**让泛型T成为一个被实化的泛型。
**reified关键字**只能用于内联函数,不能用于普通类的泛型参数也不能用作类型参数
泛型实化简化 Intent 的跳转
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
startActivity<TestActivity>(context) {
putExtra("param1", "data")
putExtra("param2", 123)
} 泛型的协变与逆变#
协变
假如定义了一个MyClass
open class Animal
class Dog : Animal()
class Cat : Animal()
// 默认:Box<T> 是不变的
class Box<T>(var item: T)
fun main() {
val dogBox: Box<Dog> = Box(Dog())
val animalBox: Box<Animal> = dogBox // ❌ 编译错误
// 虽然 Dog 是 Animal 的子类
// 但 Box<Dog> 不是 Box<Animal> 的子类
}**如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。**而要实现这一点,则 需要让MyClass类中的所有方法都不能接收T类型的参数。换句话说,T只能出现在out位置 上,而不能出现在in位置上。
class SimpleData<T> {
private var data: T? = null
fun set(t: T?) {
data = t
}
fun get(): T? {
return data
}
}
fun main() {
val student = Student("Tom", 19)
val data = SimpleData<Student>()
data.set(student)
handleSimpleData(data) // 实际上这行代码会报错,这里假设它能编译通过
val studentData = data.get()
}
// Student 作为 Person 的子类,若可以能够 协变
// 那么 handleSimpleData(SimpleData<Student>())
// 会在SimpleData<Student>()中添加 Teacher 导致编译无法通过
fun handleSimpleData(data: SimpleData<Person>) {
val teacher = Teacher("Jack", 35)
data.set(teacher)
} T只能出现在out位置 上,而不能出现在in位置上。
// 声明处协变:在定义类时声明 out
class ReadOnlyBox<out T>(private val item: T) {
fun get(): T = item
// 不能有以T为参数的方法
// fun set(value: T) { } // ❌
}
// 使用处协变:在使用时声明
interface Source<out T> {
fun get(): T
}
fun main() {
val dogBox: ReadOnlyBox<Dog> = ReadOnlyBox(Dog())
val animalBox: ReadOnlyBox<Animal> = dogBox // ✅ 允许!
// 因为T是"输出"位置
val animal: Animal = animalBox.get() // 安全
}逆变
假如定义了一个 MyClass
- 如果
Dog是Animal的子类- 那么
Consumer<Animal>是Consumer<Dog>的子类
// 声明处逆变
class Consumer<in T> {
fun consume(value: T) {
println("Consuming: $value")
}
// 不能有返回T的方法
// fun produce(): T // ❌
}
// 使用处逆变
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}需要对接口加入 in 关键字
fun main() {
val trans = object : Transformer<Person> {
override fun transform(name: String, age: Int): Person {
return Teacher(name, age)
}
}
handleTransformer(trans)
}
// 现在 Transformer<Student> 为 Transformer<Person> 的父类
// Transformer<Student> 存在 transform方法 但其返回 Teacher 导致报错
fun handleTransformer(trans: Transformer<Student>) {
val result = trans.transform("Tom", 19)
}类委托与委托属性#
委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。
类委托#
在接口声明的后面使用by关键字,再接上受委托 的辅助对象,就可以免去之前所写的一大堆模板式的代码了
委托属性#
委托属性的核心思想是将 一个属性(字段)的具体实现委托给另一个类去完成。
class MyClass {
var p by Delegate()
}
// 当调用p属性的时候会自动调用Delegate类的getValue()方法,
// 当给p属性赋值的时候会自动调用Delegate类的setValue()方法。
class Delegate {
var propValue: Any? = null
// 第一个参数用于声明该Delegate类的委托功能可以在什么类中使用
// 第二个参数KProperty<*>是Kotlin中的一个属性操作类,可用于获取各种属性相关的值
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}懒加载的实现
fun <T> later(block: () -> T) = Later(block)
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
// 值为空时采取获取
if (value == null) {
value = block()
}
return value as T
}
}
// 调用
val uriMatcher by later {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(authority, "book", bookDir)
matcher.addURI(authority, "book/#", bookItem)
matcher.addURI(authority, "category", categoryDir)
matcher.addURI(authority, "category/#", categoryItem)
matcher
} infix#
infix fun String.beginsWith(prefix: String) = startsWith(prefix) infix 实现了下述调用方式
if ("Hello Kotlin" beginsWith "Hello") {
// 处理具体的逻辑
} - 不能定义成顶层函数 。 必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某个类当中;
- 必须接收且只能接收一个参数
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) - public - 公开可见
- infix - 中缀函数,可以省略点号和括号调用
- <A, B> - 泛型参数,A 和 B 可以是任意类型
- A.to - 扩展函数,接收者是类型 A
- (that: B) - 参数是类型 B
- : Pair<A, B> - 返回值是 Pair<A, B>
- = Pair(this, that) - 函数体,创建 Pair 对象
多线程#
thread {
// 编写具体的逻辑
} 协程#
线程需要依靠操作系统层面的调度才能实现切换,协程是在编程语言层面实现的。
协程可以在单线程模式下模拟多线程编程的效果。
协程的使用#
- GlobalScope.launch 创建协程的作用域 顶层协程
fun main() {
GlobalScope.launch {
// 程序结束后协程便会结束
println("codes run in coroutine scope")
}
// 应用程序结束时协程也会结束,因此加上延时
Thread.sleep(1000)
} - runBlocking
实现了协程运行结束后整个代码才会结束。
但存在了下面问题,因此不建议使用:
- 阻塞线程违背协程设计初衷
- 可能导致性能问题 UI 界面卡顿
- 作用域管理困难
fun main() {
runBlocking {
// 协程运行结束代码才会结束
println("codes run in coroutine scope")
delay(1500)
println("codes run in coroutine scope finished")
}
} - 通过使用 launch 可以创建多个协程(在当前协程的作用域下创建子协程)
- suspend关键字,可以将任意函数声明成挂起函数 ,而挂起函数之间都是可以互相调用的 (表明该函数可以被挂起,而不阻塞线程)。但不提供协程作用域。
- 借助coroutineScope函数 , 以给任意挂起函数提供协程作用域 。
<font style="color:rgb(17, 17, 51);background-color:rgba(175, 184, 193, 0.2);">coroutineScope</font>会创建一个子协程作用域,集成父协程的上下文,父协程会自动等待所有在<font style="color:rgb(17, 17, 51);background-color:rgba(175, 184, 193, 0.2);">coroutineScope</font>中启动的子协程完成,才继续执行后续代码。如果子协程抛出异常,会取消其他子协程,并向上抛出异常。 - 虽然coroutineScope 和runBlocking 都可以 保证其作用域内的所 有代码和子协程在全部执行完之前,外部的协程会一直被挂起,但 coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不 会造成任何性能上的问题的。而runBlocking会挂起外部线程 。
fun main() {
runBlocking {
coroutineScope {
launch {
for (i in 1..10) {
println(i)
delay(1000)
}
}
}
println("coroutineScope finished")
}
println("runBlocking finished")
} 
取消协程
val job = GlobalScope.launch {
//
处理具体的逻辑
}
job.cancel() GlobalScope.launch 创建的是顶层协程,没有父级管理,不会自动取消,容易导致内存泄漏和资源浪费。
与线程管理的区别:
- ✅ 父协程可以管理运行在不同线程中的子协程
- ✅ 管理是基于逻辑关系(Job父子关系),不是基于线程
- ✅ 线程只是协程运行的"位置",不影响父子管理关系
- ✅ 父协程取消时,所有子协程无论在哪条线程都会收到取消信号
每个协程都有自己的作用域
实际使用
val job = Job()
val scope = CoroutineScope(job)
scope.launch {
// 处理具体的逻辑
}
job.cancel() 异步#
** async**: 创建一个新的子协程并返回一个Deferred对象,如果我们想要获取async函数代码块的执行结果,只需要调用Deferred对象的await() 方法即可 。
withContext() : 挂起函数
fun main() {
runBlocking {
val result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
} - 调用withContext()函数之后,会立即执行代码块中的代码,同时将外部协程挂起。当代码块中的代码全部执行完之后,会将最后一行的执行结果作为 withContext()函数的返回值返回
- withContext()函数强制要求我们指定一个线程参数
线程参数主要有以下3种值可选:Dispatchers.Default、Dispatchers.IO和 Dispatchers.Main。Dispatchers.Default表示会使用一种默认低并发的线程策略,当 你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率,此 时就可以使用Dispatchers.Default。Dispatchers.IO表示会使用一种较高并发的线程策略,当你要执行的代码大多数时间是在阻塞和等待中,比如说执行网络请求时,为了能够支持更高的并发数量,此时就可以使用Dispatchers.IO。Dispatchers.Main则表示不会开启子线程,而是在Android主线程中执行代码,但是这个值只能在Android项目中使用,纯Kotlin 程序使用这种类型的线程参数会出现错误
简化回调写法#
suspendCoroutine
suspendCoroutine 函数必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达式参数,主要作用是**将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码。**Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume()方 法或resumeWithException()可以让协程恢复执行。
suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
continuation.resume(response)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
}
// 调用
suspend fun getBaiduResponse() {
try {
val response = request("https://www.baidu.com/")
// 对服务器响应的数据进行处理
} catch (e: Exception) {
// 对异常情况进行处理
}
} 网络请求
// 定义为 Call<T> 的扩展函数
suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
if (body != null) continuation.resume(body)
else continuation.resumeWithException(
RuntimeException("response body is null"))
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
// 调用
suspend fun getAppData() {
try {
val appList = ServiceCreator.create<AppService>().getAppData().await()
// 对服务器响应的数据进行处理
} catch (e: Exception) {
// 对异常情况进行处理
}
} Retrofit 库中已直接支持
// 🆕 Retrofit 2.6.0+ 原生协程
interface AppService {
// 方式1: 直接返回数据(推荐)
@GET("apps")
suspend fun getAppData(): List<App> // Retrofit 自动处理
// 方式2: 返回完整 Response(需要更多控制时)
@GET("apps")
suspend fun getAppDataWithResponse(): Response<List<App>>
// 方式3: 返回 Result 类型(Kotlin 1.3+)
@GET("apps")
suspend fun getAppDataResult(): Result<List<App>>
}
// 使用 - 非常简单!
suspend fun loadApps() {
try {
val apps = appService.getAppData() // 直接调用
updateUI(apps)
} catch (e: Exception) {
showError(e)
}
}

