示例来自bilibili Kotlin语言深入解析 张龙老师的视频
/**
* 一个方法返回多个结果
* 方式1 返回一个集合
* 特点
* 1 方法本身是阻塞的,即主线程会进入该方法内部执行,一直执行到方法结束
* 2 集合本身是一次性返回给调用者的,即集合中的全部元素均已经获得之后才统一返回给调用端
*/
private fun myMethod(): List = listOf("hello", "world", "welcome")
fun main() {
myMethod().forEach { println(it) }
}
class HelloKotlin1 {
}
/**
* 一个方法返回多个结果
* 方式2 返回一个序列(sequence)
* 如果在获取每一个结果时都需要执行一定的计算,这种计算是一种阻塞行为,每计算出一个结果就立即返回给调用端
* 特点
* 1 序列中的数据并不像集合那样一次性返回,而是计算完一个数据后就返回
* 2 序列中的计算过程会占用主线程执行,因此会阻塞主线程
*/
private fun myMethod(): Sequence = sequence {
for (i in 100..105) {
Thread.sleep(100)
yield(i)
}
}
fun main() {
myMethod().filter { it % 2 == 0 }.map { it -> "result: $it" }.forEach { println(it) }
}
class HelloKotlin2 {
}
/**
* 一个方法返回多个结果
* 方式3 返回一个集合(结合协程)
* 特点
* 1 不会阻塞主线程
* 2 集合本身是一次性返回给调用者的
*/
private suspend fun myMethod(): List {
delay(100)
return listOf("hello", "world", "welcome")
}
fun main() = runBlocking {
myMethod().forEach { println(it) }
}
class HelloKotlin3 {
}
/**
* 一个方法返回多个结果
* 方式4 返回一个Flow
*
* 如果返回一个List 那么代表只能一次性返回所有的值。要想能够像sequence一样一次返回一个值
* 并且还不会阻塞主线程,可以使用Flow类型
*
* 特点
* 1 Flow的创建使用flow构建器
* 2 位于flow构建器中的代码是可以挂起的 而不需要额外的suspend关键字
* 3 使用emit函数发射函数
* 4 Flow里面的值通过collect来收集
*
* flow简单介绍
* flow是一种冷异步数据流,它按顺序发出值,正常或异常完成
*
* flow的构建器有如下几种
* flowOf(...) functions to create a flow from a fixed set of values.
* asFlow() extension functions on various types to convert them into flows.
* flow { ... } builder function to construct arbitrary flows from sequential calls to emit function.
* channelFlow { ... } builder function to construct arbitrary flows from potentially concurrent calls to the send function.
*
* flow有中间操作符 如map、filter、take、zip 等
* 中间操作不会执行流程中的任何代码,也不会挂起函数本身
* flow还有终端操作符 flow上的终端操作符要么是暂停函数,如 collect、single、reduce、toList 等,
* 要么是启动给定范围内流的收集的 launchIn 操作符
*
*/
private fun myMethod(): Flow = flow {
for (i in 1..4) {
delay(100)
//Thread.sleep(100) //sleep1
emit(i)
}
}
fun main() = runBlocking {
launch {
for (i in 1..4) {
println("hello in launch $i")
delay(200)
//Thread.sleep(100) //sleep2
// sleep方法不管加在那里 都会导致并行运行失效 因为Thread.sleep(100)需要睡眠主线程
// 因此加了sleep的方法就类似变成了非挂起函数
}
}
myMethod().collect {
println(it)
}
// 这里launch里面的输出和myMethod().collect并行运行
}
class HelloKotlin4 {
}
/**
* Flow的执行
*
* Flow 只有执行了终端操作符之后(例如collect) flow才会真正执行
* 例如下面 我们调用myMethod方法后 myMethod立即返回 但是其中的代码不会运行
*/
private fun myMethod(): Flow = flow {
println("myMethod execute")
for (i in 1..4) {
delay(100)
emit(i)
}
}
fun main() = runBlocking {
println("enter")
val flow = myMethod()//只调用这个方法 不会执行myMethod内部的内容
println("1111")
flow.collect {
println(it)
}
println("2222")
}
class HelloKotlin5 {
}
/**
* Flow的取消
* Flow实际上没有提供取消的方法 Flow的取消依赖于协程,如果Flow依附的协程取消了,则Flow也会取消
*
* Flow的收集操作是可以取消的,前提是Flow在一个可以取消挂起函数(如delay)中被挂起了,除此之外,我们无法
* 通过其他方式取消Flow
*/
private fun myMethod(): Flow = flow{
for (i in 1..4){
delay(100)
println("emit $i")
emit(i)
}
}
fun main() = runBlocking {
// 协程超过280毫秒会被取消
withTimeoutOrNull(280) {// 如果改成withTimeout会抛出TimeoutCancellationException
myMethod().collect {
println(it)
}
}
println("finished")
}
class HelloKotlin6 {
}
/**
* Flow builder(流构建器)
* 1 flow构建器 是经常被使用的流构建器
* 2 flowOf构建器 可以用于定义能够发射固定数量值的流
* 3 asFlow构建器 对于各种集合与序列来说,他们都提供了asFlow()扩展方法来将自身转换为Flow
*
*
*
* 非局部返回
* inline,noinline,crossinline
* non-local reutrns:非局部返回 实际上表示的是在一个方法内部,我们可以在其中通过一个lambda表达式的返回来直接将外层方法返回返回
* crossinline的作用实际上表示被标记的lambda是禁止非局部返回?
*/
fun main() = runBlocking {
// asFlow构建器可以将各种集合转为Flow 例如 LongRange IntRange IntArray Array Sequence
(1..10).asFlow().collect {
println(it)
}
println("-------")
// flowOf构建器可以用于定义能够发射固定数量值 flowOf的参数是一个可变参数
flowOf(10,20,3,4,50).collect {
println(it)
}
}
class HelloKotlin7 {
}
/**
* Flow 的中间操作符filter map
* Sequence也可以调用filter map等方法
* Flow与Sequence之间的区别:对于Flow来说 中间运算符代码内可以调用挂起函数(对比Kotlin2)
*/
private suspend fun myExecution(input: Int): String {
println("myExecution: $input")
delay(500)
return "output: $input"
}
fun main() = runBlocking {
// 将1到10转换为flow 过滤其中大于5的数字 并将这些数字形成一个映射 最后将其打印出来
(1..10).asFlow().filter { it > 5 }.map { it -> myExecution(it) }// 这里中间函数map调用了挂起函数
.collect { println(it) }
}
class HelloKotlin8 {
}
/**
* Flow 的中间运算符 transform
* transform内部可以随意处理每一个Flow元素 理论上 transform可以实现其他任意的中间操作符或者他们的组合
*
* Applies transform function to each value of the given flow.
* The receiver of the transform is FlowCollector and thus transform is a flexible function that may transform emitted element, skip it or emit it multiple times.
* This operator generalizes filter and map operators and can be used as a building block for other operators, for example:
*
* 将变换函数应用于给定Flow的每个值。
* 变换的接收者是 FlowCollector,因此变换是一个灵活的函数,可以变换发射的元素,跳过它或多次发射它。
* 此运算符概括了过滤器和映射运算符,可用作其他运算符的构建块
*/
private suspend fun myExecution(input: Int): String {
delay(500)
return "output: $input"
}
fun main() = runBlocking {
(1..10).asFlow().transform { input ->
if (input > 5){
// transform可以让flow多次发射值 collect都能收集到
emit("my input: $input")
emit(myExecution(input))
emit("hello")
}
// input为输入 emit的内容为输出
}.collect { println(it) }
}
/*
输出
my input: 6
output: 6
hello
my input: 7
output: 7
hello
my input: 8
output: 8
hello
my input: 9
output: 9
hello
my input: 10
output: 10
hello
Process finished with exit code 0
*/
class HelloKotlin9 {
}
/**
* Flow 的中间运算符 take
* 它本质是使用Kotlin的异常来实现只取出限定数量的数据‘
*
* 限定处理发射数量的中间操作符 当超出限制数量 会抛出异常
* Flow.take方法可以限制指定数量的flow中的发射的元素
*
* Returns a flow that contains first count elements.
* When count elements are consumed, the original flow is cancelled.
* Throws IllegalArgumentException if count is not positive.
* 返回包含第一个 count 元素的流。
* 当 count 元素被消耗时,原始流被取消。
* 如果 count 不是正数,则抛出 IllegalArgumentException。
*/
private fun myNumbers(): Flow = flow {
try {
emit(1)
emit(2)
println("middle")
emit(3)
emit(4)
} catch (ex: Exception) {
println(ex)
} finally {
println("finally")
}
}
fun main() = runBlocking {
myNumbers().take(2).collect {
println(it)
}
}
/*
输出
1
2
kotlinx.coroutines.flow.internal.AbortFlowException: Flow was aborted, no more elements needed
finally
Process finished with exit code 0
*/
class HelloKotlin10 {
}
/**
* Flow的终端操作符(Terminal Operation)
* Flow的终止操作符都是挂起函数 终止操作才会真正开始流的收集
*
* 主要终端操作符
* 1 除了collect 其他终端操作符 toList toSet
* 2 只获取第一个元素
* 3 reduce 将一系列值合成一个单个的值
* Accumulates value starting with the first element and applying operation to current accumulator value and each element.
* Throws NoSuchElementException if flow was empty.
* 从第一个元素开始累加值,并将操作应用于当前累加器值和每个元素。
* 如果流为空,则抛出 NoSuchElementException。
*/
fun main() = runBlocking {
val result = (1..4).asFlow()
.reduce { a, b -> a+b }
println(result)
val result1 = (1..4).asFlow()
.toList()
println(result1)
val result2 = (1..4).asFlow()
.toSet()
println(result2)
}
class HelloKotlin11 {
}
/**
* Flow 默认是顺序执行的
* 对于Flow的收集来说,它是运行在调用了终止操作符的那个协程上。默认情况下,它不会启动新的协程。
* 每个emit的元素值都会由所有的中间操作进行处理,最后再由终止操作进行处理。本质上就是每一个元素
* 依次执行 中间操作 终端操作,这样一个一个顺序执行
*/
fun main() = runBlocking {
(1..10).asFlow().filter {// 有的元素在第一步中就被过滤了
println("第一步 filter: $it")
it % 2 == 0
}.map {
println("第二步 map: $it")
"Hello: $it"
}.collect {
println("第三步 final: $it")
println("=============")
}
}
class HelloKotlin12 {
}
/**
* Flow Context(Flow 上下文)
* Flow的收集总是发生在调用协程的上下文中,这个特性叫做上下文保留(Context Preservation)
* 注意加上-Dkotlinx.coroutines.debug
*/
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
private fun myMethod(): Flow = flow {
log("start")
for (i in 1..4) {
emit(i)
}
}
fun main() = runBlocking {
// 例如下面的Flow的收集操作发生在runBlocking的上下文 即主线程中
myMethod().collect {
log("Collected: $it")
}
}
/*
输出
[main @coroutine#1] start
[main @coroutine#1] Collected: 1
[main @coroutine#1] Collected: 2
[main @coroutine#1] Collected: 3
[main @coroutine#1] Collected: 4
*/
class HelloKotlin13 {
}
/**
* 尝试切换Flow上下文
*/
private fun myMethod(): Flow = flow {
// 尝试将Flow的发射上下文更改
withContext(Dispatchers.Default){
for (i in 1..4) {
emit(i)
}
}
}
fun main() = runBlocking {
myMethod().collect {
println(it)
}
}
/*
输出
Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
Flow was collected in [BlockingCoroutine{Active}@76aa9e23, BlockingEventLoop@5fcb12cd],
but emission happened in [DispatchedCoroutine{Active}@1f1d9c3, Dispatchers.Default].
Please refer to 'flow' documentation or use 'flowOn' instead
报错原因 违反了Flow的不变性
Flow收集发生在[BlockingCoroutine{Active}@76aa9e23, BlockingEventLoop@5fcb12cd]
Flow发射发生在[DispatchedCoroutine{Active}@1f1d9c3, Dispatchers.Default]
要实现这样的需求需要使用flowOn代替
*/
class HelloKotlin14 {
}
/**
* flowOn 切换Flow上下文
*
* 借助flowOn,可以让Flow在发射元素所在的上下文与收集(终端操作符)所处的上下文是不同的。
* 值得注意的是:flowOn运算符改变了Flow的顺序性。
* 现在 收集操作发生在一个协程上,而发射操作发射在另外的协程
* flowOn运算符本质上会改变上下文中的CoroutineDispatcher,并且为上游的flow创建另外一个协程
* 上游可以简单理解为发生在当前操作的前面 不过 终端操作即使发生在中间操作之前,也不能称之为上游
*
* 注意加上-Dkotlinx.coroutines.debug
*/
private fun log(logMessage: String) = println("[${Thread.currentThread().name}] $logMessage")
private fun myMethod(): Flow = flow {
for (i in 1..4) {
Thread.sleep(100)
log("emit: $i")
emit(i)
}
}.flowOn(Dispatchers.Default)// 对比14 的差异 在这里切换emit的上下文
fun main() = runBlocking {
myMethod().collect {
value -> log("Collected $value")
}
}
/*
输出
[DefaultDispatcher-worker-1 @coroutine#2] emit: 1
[main @coroutine#1] Collected 1
[DefaultDispatcher-worker-1 @coroutine#2] emit: 2
[main @coroutine#1] Collected 2
[DefaultDispatcher-worker-1 @coroutine#2] emit: 3
[main @coroutine#1] Collected 3
[DefaultDispatcher-worker-1 @coroutine#2] emit: 4
[main @coroutine#1] Collected 4
可以看到Flow的发射与收集工作在不同的协程
*/
class HelloKotlin15 {
}