kotlin小结

一、协程相关

1-1、协程使用

官网例子(组合挂起函数):https://www.kotlincn.net/docs/reference/coroutines/composing-suspending-functions.html

1、顺序调用:

suspend fun doSomethingUsefulOne(): Int {

    delay(1000L) // 假设我们在这里做了一些有用的事

    return 13

}

suspend fun doSomethingUsefulTwo(): Int {

    delay(1000L) // 假设我们在这里也做了一些有用的事

    return 29

}

我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 顺序 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:

val time = measureTimeMillis {

    val one = doSomethingUsefulOne()

    val two = doSomethingUsefulTwo()

    println("The answer is ${one + two}")

}

println("Completed in $time ms")

输出:The answer is 42 Completed in 2017 ms

2、使用Async并发

如果 doSomethingUsefulOne 与 doSomethingUsefulTwo 之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。

在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch 返回一个 Job 并且不附带任何结果值,而 async 返回一个 Deferred —— 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。你可以使用 .await() 在一个延期的值上得到它的最终结果, 但是 Deferred 也是一个 Job,所以如果需要的话,你可以取消它。

suspend fun concurrentSum(): Int = coroutineScope {

    val one = async { doSomethingUsefulOne() }

    val two = async { doSomethingUsefulTwo() }

    one.await() + two.await()

}

输出:The answer is 42 Completed in 1017 ms

这里快了两倍,因为两个协程并发执行。

3、协程抛出异常处理

如果在 concurrentSum 函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。

fun main() = runBlocking<Unit> {

    try {

        failedConcurrentSum()

    catch(e: ArithmeticException) {

        println("Computation failed with ArithmeticException")

    }

}

suspend fun failedConcurrentSum(): Int = coroutineScope {

    val one = async<Int> {

        try {

            delay(Long.MAX_VALUE) // 模拟一个长时间的运算

            42

        finally {

            println("First child was cancelled")

        }

    }

    val two = async<Int> {

        println("Second child throws an exception")

        throw ArithmeticException()

    }

    one.await() + two.await()

}

运行结果:

Second child throws an exception

First child was cancelled

Computation failed with ArithmeticException

1-2、suspend挂起概念

仍物线(朱凯):https://kaixue.io/kotlin-coroutines-2/

suspend是协程的关键字,每一个被suspend修饰的方法都必须在另一个suspend函数或者Coroutine协程程序中进行调用。

1、挂起的本质:

协程中「挂起」的对象到底是什么?挂起线程,还是挂起函数?都不对,我们挂起的对象是协程。

还记得协程是什么吗?启动一个协程可以使用 launch 或者 async 函数,协程其实就是这两个函数中闭包的代码块。

launch ,async 或者其他函数创建的协程,在执行到某一个 suspend 函数的时候,这个协程会被「suspend」,也就是被挂起。

那此时又是从哪里挂起?从当前线程挂起。换句话说,就是这个协程从正在执行它的线程上脱离。

注意,不是这个协程停下来了!是脱离,当前线程不再管这个协程要去做什么了。

suspend 是有暂停的意思,但我们在协程中应该理解为:当线程执行到协程的 suspend 函数的时候,暂时不继续执行协程代码了。

我们先让时间静止,然后兵分两路,分别看看这两个互相脱离的线程和协程接下来将会发生什么事情:

挂起后线程接着干了啥:

线程执行到了 suspend 函数这里的时候,就暂时不再执行剩余的协程代码,执行协程代码块以外的工作。

挂起后协程干了啥:

它从 suspend 函数开始脱离启动它的线程,继续执行在 Dispatchers

协程的代码在到达 suspend 函数的时候被掐断,接下来协程会从这个 suspend 函数开始继续往下执行,不过是在指定的线程

协程在执行到有 suspend 标记的函数的时候,会被 suspend 也就是被挂起,而所谓的被挂起,就是切个线程;

不过区别在于,挂起函数在执行完成之后,协程会重新切回它原先的线程

再简单来讲,在 Kotlin 中所谓的挂起,就是一个稍后会被自动切回来的线程调度操作

2、协程与线程

下面来看下官方的原话:协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。

只不过呢,协程它不仅提供了方便的 API,在设计思想上是一个基于线程的上层框架,你可以理解为新造了一些概念用来帮助你更好地使用这些 API,仅此而已。类似于 Java 自带的 Executor 系列 API 或者 Android 的 Handler 系列 API。

说到这里,Kotlin 协程的三大疑问:协程是什么、挂起是什么、挂起的非阻塞式是怎么回事,非常简单:

  • 协程就是切线程;
  • 挂起就是可以自动切回来的切线程;
  • 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作

二、更好调用的函数(默认参数值、@JvmOverloads、显示参数名)

Kotlin的函数更加好调用,主要是表现在两个方面:1,显式的标示参数名,可以方便代码阅读;2,函数可以有默认参数值,可以大大减少Java中的函数重载

@JvmOverloads

fun joinToString(

    param1: String = "",

    param2: String,

    param3: String = "",

    param4: String = ""

): String {

    return StringBuilder()

        .append(param1)

        .append(param2)

        .append(param3)

        .append(param4)

        .toString()

}

在kotlin中调用:

第二个参数没有默认值,则调用时必须有一个参数

joinToString("aaa", param3 = "ccc", param2 = "ddd")

joinToString("aaa""bbb""ccc""ddd")

joinToString("aaa""ccc""ddd")

joinToString(param2 = "aaa")

在java中调用:

在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了,还是需要给所有参数赋值

这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载,

添加@JvmOverloads后,反编译后可以看到帮助我们生成多个参数的重载函数,

然后在java中也可以如下调用,

注意:

  1. 原函数第二个参数没有默认值,因此这里如果只传一个参数则默认是第二个参数:
  2. java中无法指定参数位置

三、使用data class来快速实现model类

在java中要声明一个model类需要实现很多的代码,首先需要将变量声明为private,然后需要实现get和set方法,还要实现对应的hashcode equals toString方法等

如果用kotlin,只需要一行代码就可以做到。

/**

 * Kotlin会为类的参数自动实现get set方法

 * */

class User(val name: String, val age: Int, val gender: Int, var address: String)

/**

 * 用data关键词来声明一个数据类,除了会自动实现get set,还会自动生成equals hashcode toString

 * */

data class User2(val name: String, val age: Int, val gender: Int, var address: String)

对于Kotlin中的类,会为它的参数自动实现get set方法。而如果加上data关键字,还会自动生成equals hashcode toString。原理其实数据类中的大部分代码都是模版代码,Kotlin聪明的将这个模版代码的实现放在了编译器处理的阶段。

坑点:

data class使用fastJson转换数据时会报如下错误,fastjson反射获取无参构造函数,而data class至少有一个有参构造函数,

com.alibaba.fastjson.JSONException: default constructor not found. class cn.ac.ia.iot.www.telemedicine.mvp.model.bean.DataClass

at com.alibaba.fastjson.parser.JavaBeanInfo.build(JavaBeanInfo.java:496)

at com.alibaba.fastjson.parser.JavaBeanDeserializer.(JavaBeanDeserializer.java:35)

at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:229)

at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:148)

at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:683)

at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:659)

at com.alibaba.fastjson.JSON.parseObject(JSON.java:238)

at com.alibaba.fastjson.JSON.parseObject(JSON.java:210)

at com.alibaba.fastjson.JSON.parseObject(JSON.java:169)

at com.alibaba.fastjson.JSON.parseObject(JSON.java:278)

解决方案:

1、无法使用dataclass,使用普通class即可,手写toString

2、替换为Gson等其他库

3、使用fastJson某一低版本、所有参数添加默认值生成无参构造等方式(试了生成了无参构造,但仍报错)

https://www.dazhuanlan.com/2019/10/16/5da62b6c48adc/

https://www.jianshu.com/p/38219b28efb9

四、扩展函数

4-1、自定义的扩展函数

例一:如String类中,我们想获取最后一个字符,String中没有这样的直接函数,你可以用.后声明这样一个扩展函数:

//扩展函数

fun String.lastChar(): Char = this.get(this.length - 1)

//使用

fun testFunExtension() {

    val str = "test extension fun";

    println(str.lastChar())

}

这样定义好lastChar()函数后,之后只需要import进来后,就可以用String类直接调用该函数了,跟调用它自己的方法没有区别。这样可以避免重复代码和一些静态工具类,而且代码更加简洁明了。

例二:协程和视图生命周期相绑定

若协程用于异步加载一张图片,这张图片显示在 ImageView 上。当 ImageView 所在界面已被销毁时,得及时取消协程的加载任务,以释放资源:

fun Job.autoDispose(view: View) {

    val isAttached = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && view.isAttachedToWindow || view.windowToken != null

    if (!isAttached){

        cancel()

    }

    // 监听视图生命周期

    val listener = object : View.OnAttachStateChangeListener {

        // 在视图生命周期结束时取消协程

        override fun onViewDetachedFromWindow(v: View?) {

            cancel()

            v?.removeOnAttachStateChangeListener(this)

        }

        override fun onViewAttachedToWindow(v: View?) = Unit

    }

    view.addOnAttachStateChangeListener(listener)

    invokeOnCompletion {

        view.removeOnAttachStateChangeListener(listener)

    }

}

为Job扩展方式,传入 View 类型的参数,表示该Job和该 View 的生命周期绑定,当 View 生命周期结束时,自动取消协程。

4-2、Kotlin的源码中定义的常用的扩展函数also、apply、let、run

apply函数的作用是:调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。 从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。 apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。

函数

是否是扩展函数

函数参数(this、it)

返回值(调用本身、最后一行)

with 不是 this 最后一行
T.run this 最后一行
T.let it 最后一行
T.also it 调用本身
T.apply this 调用本身

五、懒初始化byLazy和延迟初始化lateinit

5-1 懒初始化by lazy

懒初始化是指推迟一个变量的初始化时机,变量在使用的时候才去实例化,这样会更加的高效。因为我们通常会遇到这样的情况,一个变量直到使用时才需要被初始化,或者仅仅是它的初始化依赖于某些无法立即获得的上下文。

5-2 延迟初始化lateinit

另外,对于var的变量,如果类型是非空的,是必须初始化的,不然编译不通过,这时候需要用到lateinit延迟初始化,使用的时候再去实例化。

5-3 by lazy 和 lateinit 的区别

  • by lazy 修饰val的变量

  • lateinit 修饰var的变量,且变量是非空的类型

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/112695007
今日推荐