Kotlin核心基础之高阶函数,协程基本用法

2024-12-20  本文已影响0人  Owen270

怎么理解kotlin的挂起函数?

Kotlin协程进阶使用
Kotlin协程在工作中有用过吗? (qq.com)

1.kotlin和java编译后都是java字节码,相对于java有啥优势和劣势?

(1).kotlin优势

(2).kotlin劣势
kotlin编译速度相对java较慢,因为她需要进行额外的类型检查(空安全),和代码转换(kotlin-java-java字节码,复杂,耗性能)

备注:Kotlin与Java在运行时性能方面基本相当,由于kotlin支持inline函数,lambda表达式,在某些情况下性能还要由于java.

2.Kotlin内置的高阶函数run的原理是什么,与let函数有啥区别?

(1).inline :表示这是一个内联函数,将函数代码直接插入调用处,避免了调用普通方法时,栈帧的创建,销毁所带来的开销。
(2).<T,R> T.run :T代表要为T扩展出一个名为run的函数,R表示lambda表达式最后一行返回的类型。
(3).block:T.()->R:
block:表示lambda的名称
T.():表示输入参数是T本身,让lambda表达式持有了this表示(T)调用者本身
R:表示lambda最后一行返回的类型.
(T) :表示输入参数是T本身,让lambda表达式持有了it表示(T)调用者本身

public inline fun<T,R> T.let(block:(T)->R){
    return block(this)
}

public inline fun<T,R> T.run(block:T.()->R):R{
      return block()
}
//with不是泛型的扩展方法,而是全局方法
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}



fun main(){
   var  result1="778".run{  
         true
        length
    }
   println(result1)   //3
  var result2="778".let{
         999
         "$it" //778
  }
}

为啥所有的类型都可以使用run或者let,那是因为高阶函数let和run是对泛型进行扩展,意味着所有类型都等于泛型,所以任何地方都可以使用.run和let的区别在于lambda表达式一个持有this表示调用者本身,一个持有it表示调用者本身。

public inline <T> T.apply(block:T.()->unit):T
public inline <T> T.also(block:(T)->unit):T
public int <T,R> with(receiver:T,block:T.()->R):R

3.kotlin语言泛型的形变是什么?【PECS原则 extends out || super in】

class Student<T> {}   
ArrayList<out T>            
ArrayList<in T>     

4.协程的基本使用.

(1).启动协程的几种方式.
public fun CoroutineScope.launch(
     context:CoroutineScope=EmptyCoroutineContext,
     start:CoroutineStart = CoroutineStart.DEFAULT,
     block:suspend CoroutineScope.()->Unit
):Job {  //Job是返回值, {} 是launch的方法体实现 【不要搞混淆了】
 //launch方法体实现,返回Job对象
      val newcontenxt=newCoroutineContext(context)
     val coroutine=if(start.isLazy){
          LazyStandaloneCoroutine(newContext,block);//实现了job接口
     }else{
        StandaloneCoroutine(newContext,active=true)  //实现了job接口
    }
     coroutine.start(start,coroutine,block)  //启动job
    return  coroutine;//返回实现了Job接口的协程对象
}

Job.cancel()可以用来取消协程,控制协程的生命周期.

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine= if (start.isLazy){
        LazyDeferredCoroutine<T>(newContext, block)  //实现了Deferred接口
    } else{
       DeferredCoroutine<T>(newContext, active = true) //实现了Deferred接口
    }
    coroutine.start(start, coroutine, block)   //启动协程
    return coroutine //返回实现了Deferred接口的协程对象
}
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    return coroutine.joinBlocking()
}
//这一段代码会阻塞主线程,直接导致黑屏。
runBlocking {
     delay(10000)
 }
//即使指定启动的协程运行在IO线程,也会阻塞主线程,导致黑屏
runBlocking(Dispatchers.IO) {
   delay(10000L)
}
//依然会阻塞主线程导致黑屏
 GlobalScope.launch(Dispatchers.Main){
       runBlocking {
                delay(10000)
        }

 }
//不会阻塞主线程
 GlobalScope.launch(Dispatchers.IO){
       runBlocking {
                delay(10000)
        }
 }
(2).withContext() suspend修饰,并不会启动协程,只能在suspend挂起方法或者协程中调用,用于切换线程,并挂起协程。(暂停协程,但是执行协程的线程可以继续执行其他任务,不会阻塞,等到协程恢复,该线程可以继续执行)
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T
   
 lifecycleScope.launch(Dispatchers.IO) {
            delay(1000)
            withContext(Dispatchers.Main){ //返回值是Unit 也就是void  //切换到主线程 ,此时协程的IO线程可以去执行其他任务
                println("Test001:withContext:${Thread.currentThread().name}")
            }
        }
(3).coroutineScope() suspend修饰,并不会启动协程,只能在suspend挂起方法或者协程中调用,创建一个新的协程作用域【或者说可以在已有的协程内部创建一个新协程】
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
 }


coroutineScope {  }  //报错 ,不能直接用,只能在协程里面使用

runBlocking { //非suspend全局方法,直接启动协程
   coroutineScope {  }   //正常使用,因为runBlocking创建了协程//创建一个新的协程作用域
  delay(1000L) 
}

(4).协程上下文,一般用调度器Dispatchers来切换线程,它是CoroutineContext接口的实现类.

5.协程的挂起和阻塞

(1).suspend非阻塞式挂起函数
image.png
fun testCoroutineInActivity() {
       GlobalScope.launch(Dispatchers.Main) { //1.启动协程1
           println("Test001:执行在协程中...")
           GlobalScope.launch (Dispatchers.IO){//2.启动协程2
               println("Test001:异步执行result1")
               delay(1000)   
               println("Test001:result1:1234")
           }
           GlobalScope.launch(Dispatchers.IO) {//3.启动协程3
               println("Test001:异步执行result2.")
               delay(1000) 
               println("Test001:result2:123456")
           }
           println("Test001:执行完毕...")
       }
}
注意:协程2和协程3中的delay函数只是挂起了协程2和协程3,不会影响协程1中的主线程的执行.

执行结果如下:


image.png
(2).协程都被挂起了,那么挂起函数的函数体由谁执行呢?

当协程执行到挂起函数式,协程的执行会被暂停(即协程被挂起),但它并不会阻塞执行该挂起函数的线程,挂起函数的函数体任然由当前线程继续执行。
为了更清楚的解释这一点,我们可以使用一下步骤:
(1).协程启动:当一个协程开始运行时,它会在某个线程(Dispatchers.Main)上执行。
(2).遇到挂起函数:当协程遇到挂起函数时,协程的执行会被暂停。
(3).挂起函数执行:尽管协程被暂停了,挂起函数的函数体任然会在原始线程上执行。(除非该挂起函数明确指定了其他的执行上下文)
(4).挂起函数完成:一旦挂起函数完成其工作,它会通知协程库,然后协程会恢复执行。
注意:当协程被挂起时,主线程并没有被阻塞,而是可以执行其他的任务,等到挂起函数执行完毕,协程恢复时,又可以在主线程中继续执行。
总结一下:在协程中使用挂起函数时,任何可能得"阻塞"操作都会转移到其他线程上执行,这样启动协程的原始线程(例如主线程)就不会被实际阻塞,这样使得协程特别适合UI线程编程,因为它可以确保UI线程保持响应。

6.kotlin协程在工作中有用过吗?

kotlin协程是一个线程框架,提供了一种轻量级的并发处理方式,通过非阻塞挂起和恢复实现了用同步代码的方式编写异步代码,把原本运行在不同线程的代码写在一个代码块{}里面,看起来就像是同步代码。
协程的目的是,简化复杂的异步代码逻辑,用同步的代码编写方式实现复杂异步代码逻辑。

(1).几种封装好的协程

协程就是一个线程框架,是对线程的封装,提供了一种轻量并发的处理方式,通过非阻塞式挂起和恢复的方式,用同步代码的方编写式实现复杂的异步代码逻辑,把原本运行在不同线程的代码写在一个代码块里面,看起来就像同步代码。

(2).如果一个页面需要同时并发请求多个接口,当所有的接口都请求完成需要做一些合并处理,然后更新UI,如何并发处理呢?
   fun testRxjavaZip(){
        println("-------------")
        val observable1: Observable<HttpResult<List<Banner>>> = HttpRetrofit.apiService.getBanners().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        val observable2:Observable<HttpResult<ArrayList<HomeData.DatasBean>>> =HttpRetrofit.apiService.getTopArticles().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        val observable3: Observable<HttpResult<List<KnowledgeData>>> = HttpRetrofit.apiService.getKnowledgeTree().subscribeOn(Schedulers.io()).observeOn(
            AndroidSchedulers.mainThread())
        var result1: HttpResult<List<Banner>>? =null
        var result2: HttpResult<ArrayList<HomeData.DatasBean>>? =null
        var result3: HttpResult<List<KnowledgeData>>? =null
        Observable.zip(observable1, observable2, observable3,
            object: Function3<
                    HttpResult<List<Banner>>,
                    HttpResult<ArrayList<HomeData.DatasBean>>,
                    HttpResult<List<KnowledgeData>>,
                    Boolean>{
                override fun apply(t1: HttpResult<List<Banner>>, t2: HttpResult<ArrayList<HomeData.DatasBean>>, t3: HttpResult<List<KnowledgeData>>): Boolean {
                    result1=t1
                    result2=t2
                    result3=t3
                    return t1!=null&&t2!=null&&t3!=null
                }
            }).subscribe(object:Observer<Boolean>{
            override fun onSubscribe(d: Disposable) {

            }

            override fun onError(e: Throwable) {

            }

            override fun onComplete() {

            }

            override fun onNext(t: Boolean) {
                if(t){//对结果进行处理
                    println("成功获取结果");
                    println(Gson().toJson(result1))
                    println(Gson().toJson(result2))
                    println(Gson().toJson(result3))
                }
            }
        });

    }
    fun testCoroutineInActivity() {
      //1.launch启动协程,返回Job对象,通过job.cancel()取消任务
     val job:Job=CoroutineScope(Dispatchers.IO).launch() { 
            println("Test001:查看运行的线程1:"+Thread.currentThread().name)
            val start= System.currentTimeMillis()
            val result1=async {//运行在主线程,非异步
                println("Test001:查看运行的线程2:"+Thread.currentThread().name)
                delay(1000)
                "123"
            }
         //2.async  启动协程,得到有返回值的Deferred对象
            val result2:Deferred<String> = async(Dispatchers.IO) { //异步 
                println("Test001:查看运行的线程3:"+Thread.currentThread().name)
                delay(2000)
                 "456"

            }
            val result3=result1.await()+result2.await();
            println("Test001:并发耗时:"+ (System.currentTimeMillis() - start)+"||result3:"+result3)
            withContext(Dispatchers.IO){
                println("Test001:查看运行的线程4:"+ Thread.currentThread().name)
                delay(1000)
            }
            println("Test001:查看运行的线程5:"+ Thread.currentThread().name)
        }
    }
// 注意:在真实开发过程中,MainScope作用域用的非常常用
MainScope().launch(){     // 注意:此协程块默认是在UI线程中启动协程
    // 下面的代码看起来会以同步的方式一行行执行(异步代码同步获取结果)
    val token = apiService.getToken()   // 网络请求:IO线程,获取用户token
    val user = apiService.getUser(token)// 网络请求:IO线程,获取用户信息
    nameTv.text = user.name             // 更新 UI:主线程,展示用户名
    val articleList = apiService.getArticleList(user.id)// 网络请求:IO线程,根据用户id获取用户的文章集合哦
    articleTv.text = "用户${user.name}的文章页数是:${articleList.size}页"   // 更新 UI:主线程
}
上一篇 下一篇

猜你喜欢

热点阅读