Kotlin中一些知识点学习
来源:互联网 发布:淘宝助理宝贝图片尺寸 编辑:程序博客网 时间:2024/06/04 19:08
1. 协程
github地址:kotlinx.coroutines(https://github.com/kotlin/kotlinx.coroutines)
fun main(args: Array<String>) { launch(CommonPool) { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L)}/* 运行结果: ("Hello,"会立即被打印, 1000毫秒之后, "World!"会被打印)Hello, World!*/
解释一下delay方法:
在协程里delay方法作用等同于线程里的sleep, 都是休息一段时间, 但不同的是delay不会阻塞当前线程, 而像是设置了一个闹钟, 在闹钟未响之前, 运行该协程的线程可以被安排做了别的事情, 当闹钟响起时, 协程就会恢复运行.
协程启动后还可以取消:
launch方法有一个返回值, 类型是Job, Job有一个cancel方法, 调用cancel方法可以取消协程, 看一个数羊的例子:
fun main(args: Array<String>) { val job = launch(CommonPool) { var i = 1 while(true) { println("$i little sheep") ++i delay(500L) // 每半秒数一只, 一秒可以输两只 } } Thread.sleep(1000L) // 在主线程睡眠期间, 协程里已经数了两只羊 job.cancel() // 协程才数了两只羊, 就被取消了 Thread.sleep(1000L) println("main process finished.")}
运行结果是,如果不调用cancel, 可以数到4只羊:
1 little sheep2 little sheepmain process finished.
注意还有一个方法:job.join() // 持续等待,直到子协程执行完成
1.1 理解suspend方法
suspend方法的语法很简单, 只是比普通方法只是多了个suspend关键字:
suspend fun foo(): ReturnType { // ...}
suspend方法只能在协程里面调用, 不能在协程外面调用.
suspend方法本质上, 与普通方法有较大的区别, suspend方法的本质是异步返回(注意: 不是异步回调).
现在, 我们先来看一个异步回调的例子:
fun main(...) { requestDataAsync { println("data is $it") } Thead.sleep(10000L) // 这个sleep只是为了保活进程}fun requestDataAsync(callback: (String)->Unit) { Thread() { // do something need lots of times. callback(data) }.start()}
逻辑很简单, 就是通过异步的方法拉一个数据, 然后使用这个数据, 按照以往的编程方式, 若要接受异步回来的数据, 唯有使用callback.
但是假如使用协程, 可以不使用callback, 而是直接把这个数据”return”回来, 调用者不使用callback接受数据, 而是像调用同步方法一样接受返回值. 如果上述功能改用协程, 将会是:
fun main(...) { launch(Unconfined) { // 请重点关注协程里是如何获取异步数据的 val data = requestDataAsync() // 异步回来的数据, 像同步一样return了 println("data is $it") } Thead.sleep(10000L) // 请不要关注这个sleep}suspend fun requestDataAsync() { // 请注意方法前多了一个suspend关键字 return async(CommonPool) { // 先不要管这个async方法, 后面解释 // do something need lots of times. // ... data // return data, lambda里的return要省略 }.await()}
这里, 我们首先将requestDataAsync转成了一个suspend方法, 其原型的变化是:
1.在前加了个suspend关键字.
2.去除了原来的callback参数.
这是怎么做到的呢?
当程序执行到requestDataAsync内部时, 通过async启动了另外一个新的子协程去拉取数据, 启动这个新的子协程后, 当前的父协程就挂起了, 此时requestDataAsync还没有返回.子协程一直在后台跑, 过了一段时间, 子协程把数据拉回来之后, 会恢复它的父协程, 父协程继续执行, requestDataAsync就把数据返回了.
为了加深理解, 我们来对比一下另一个例子: 不使用协程, 将异步方法也可以转成同步的方法(在单元测试里, 我们经常这么做):
fun main(...) { val data = async2Sync() // 数据是同步返回了, 但是线程也阻塞了 println("data is $it") // Thead.sleep(10000L) // 这一句在这里毫无意义了, 注释掉}private var data = ""private fun async2Sync(): String { val obj = Object() // 随便创建一个对象当成锁使用 requestDataAsync { data -> this.data = data // 暂存data synchronized(locker) { obj.notifyAll() // 通知所有的等待者 } } obj.wait() // 阻塞等待 return this.data}fun requestDataAsync(callback: (String)->Unit) { // ...普通的异步方法}
注意对比上一个协程的例子, 这样做表面上跟它是一样的, 但是这里main方法会阻塞的等待async2Sync()方法完成. 同样是等待, 协程就不会阻塞当前线程, 而是自己主动放弃执行权, 相当于遣散当前线程, 让它去干别的事情去.
为了更好的理解这个”遣散”的含义, 我们再来看一个例子:
fun main(args: Array<String>) { // 1. 程序开始 println("${Thread.currentThread().name}: 1"); // 2. 启动一个协程, 并立即启动 launch(Unconfined) { // Unconfined意思是在当前线程(主线程)运行协程 // 3. 本协程在主线程上直接开始执行了第一步 println("${Thread.currentThread().name}: 2"); /* 4. 本协程的第二步调用了一个suspend方法, 调用之后, * 本协程就放弃执行权, 遣散运行我的线程(主线程)请干别的去. * * delay被调用的时候, 在内部创建了一个计时器, 并设了个callback. * 1秒后计时器到期, 就会调用刚设置的callback. * 在callback里面, 会调用系统的接口来恢复协程. * 协程在计时器线程上恢复执行了. (不是主线程, 跟Unconfined有关) */ delay(1000L) // 过1秒后, 计时器线程会resume协程 // 7. 计时器线程恢复了协程, println("${Thread.currentThread().name}: 4") } // 5. 刚那个的协程不要我(主线程)干活了, 所以我继续之前的执行 println("${Thread.currentThread().name}: 3"); // 6. 我(主线程)睡2秒钟 Thread.sleep(2000L) // 8. 我(主线程)睡完后继续执行 println("${Thread.currentThread().name}: 5");}
运行结果:
main: 1main: 2main: 3kotlinx.coroutines.ScheduledExecutor: 4main: 5
上述代码的注释详细的列出了程序运行流程, 看完之后, 应该就能明白 “遣散” 和 “放弃执行权” 的含义了.
Unconfined的含义是不给协程指定运行的线程, 逮到谁就使用谁, 启动它的线程直接执行它, 但被挂起后, 会由恢复它的线程继续执行, 如果一个协程会被挂起多次, 那么每次被恢复后, 都可能被不同线程继续执行.
现在再来回顾刚刚那句: suspend方法的本质就是异步返回.含义就是将其拆成 “异步” + “返回”:
首先, 数据不是同步回来的(同步指的是立即返回), 而是异步回来的.
其次, 接受数据不需要通过callback, 而是直接接收返回值.
调用suspend方法的详细流程是:
在协程里, 如果调用了一个suspend方法, 协程就会挂起, 释放自己的执行权, 但在协程挂起之前, suspend方法内部一般会启动了另一个线程或协程, 我们暂且称之为”分支执行流”吧, 它的目的是运算得到一个数据.当suspend方法里的*分支执行流”完成后, 就会调用系统API重新恢复协程的执行, 同时会数据返回给协程(如果有的话).
为什么不能再协程外面调用suspend方法?
suspend方法只能在协程里面调用, 原因是只有在协程里, 才能遣散当前线程, 在协程外面, 不允许遣散, 反过来思考, 假如在协程外面也能遣散线程, 会怎么样, 写一个反例:
fun main(args: Array<String>) { requestDataSuspend(); doSomethingNormal();}suspend fun requestDataSuspend() { // ... }fun doSomethingNormal() { // ...}
requestDataSuspend是suspend方法, doSomethingNormal是正常方法, doSomethingNormal必须等到requestDataSuspend执行完才会开始, 如果main方法失去了并行的能力, 所有地方都失去了并行的能力, 这肯定不是我们要的, 所以需要约定只能在协程里才可以遣散线程, 放弃执行权, 于是suspend方法只能在协程里面调用.
协程创建后, 并不总是立即执行, 要分是怎么创建的协程, 通过launch方法的第二个参数是一个枚举类型CoroutineStart, 如果不填, 默认值是DEFAULT, 那么协程创建后立即启动, 如果传入LAZY, 创建后就不会立即启动, 直到调用Job的start方法才会启动.
在协程里, 所有接受callback的方法, 都可以转成不需要callback的suspend方法,上面的requestDataSuspend方法就是一个这样的例子, 我们回过头来再看一眼:
suspend fun requestDataSuspend() { return async(CommonPool) { // do something need lots of times. // ... data // return data }.await()}
其内部通过调用了async和await方法来实现(关于async和await我们后面再介绍), 这样虽然实现功能没问题, 但并不最合适的方式, 上面那样做只是为了追求最简短的实现, 合理的实现应该是调用suspendCoroutine方法, 大概是这样:
suspend fun requestDataSuspend() { suspendCoroutine { cont -> // ... 细节暂时省略 }}// 可简写成:suspend fun requestDataSuspend() = suspendCoroutine { cont -> // ...}
在完整实现之前, 需要先理解suspendCoroutine方法, 它是Kotlin标准库里的一个方法, 原型如下:
suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T
现在来完善一下刚刚的例子:
suspend fun requestDataSuspend() = suspendCoroutine { cont -> requestDataFromServer { data -> // 普通方法还是通过callback接受数据 if (data != null) { cont.resume(data) } else { cont.resumeWithException(MyException()) } }}/** 普通的异步回调方法 */fun requestDataFromServer(callback: (String)->Unit) { // ... get data from server, it will call back when finished.}
suspendCoroutine有个特点:
suspendCoroutine { cont -> // 如果本lambda里返回前, cont的resume和resumeWithException都没有调用 // 那么当前执行流就会挂起, 并且挂起的时机是在suspendCoroutine之前 // 就是在suspendCoroutine内部return之前就挂起了 // 如果本lambda里返回前, 调用了cont的resume或resumeWithException // 那么当前执行流不会挂起, suspendCoroutine直接返回了, // 若调用的是resume, suspendCoroutine就会像普通方法一样返回一个值 // 若调用的是resumeWithException, suspendCoroutine会抛出一个异常 // 外面可以通过try-catch来捕获这个异常}
回过头来看一下, 刚刚的实现有调用resume方法吗, 我们把它折叠一下:
suspend fun requestDataSuspend() = suspendCoroutine { cont -> requestDataFromServer { ... }}
清晰了吧, 没有调用, 所以suspendCoroutine还没有返回之前就挂起了, 但是挂起之前lambda执行完了, lambda里调用了requestDataFromServer, requestDataFromServer里启动了真正做事的流程(异步执行的), 而suspendCoroutine则在挂起等待.
等到requestDataFromServer完成工作, 就会调用传入的callback, 而这个callback里调用了cont.resume(data), 外层的协程就恢复了, 随后suspendCoroutine就会返回, 返回值就是data.
1.2 async/await模式:
我们前面多次使用了launch方法, 它的作用是创建协程并立即启动, 但是有一个问题, 就是通过launch方法创建的协程都没办法携带返回值. async之前也出现过, 但一直没有详细介绍.
async方法作用和launch方法基本一样, 创建一个协程并立即启动, 但是async创建的协程可以携带返回值.
launch方法的返回值类型是Job, async方法的返回值类型是Deferred, 是Job的子类, Deferred里有个await方法, 调用它可得到协程的返回值.
async/await是一种常用的模式, async的含义是启动一个异步操作, await的含义是等待这个异步操作结果.
是谁要等它啊, 在传统的不使用协程的代码里, 是线程在等(线程不干别的事, 就在那里傻等). 在协程里不是线程在等, 而且是执行流在等, 当前的流程挂起(底下的线程会被遣散去干别的事), 等到有了运算结果, 流程才继续运行.
所以我们又可以顺便得出一个结论: 在协程里执行流是线性的, 其中的步骤无论是同步的还是异步的, 后面的步骤都会等前面的步骤完成.
我们可以通过async起多个任务, 他们会同时运行, 我们之前使用的async姿势不是很正常, 下面看一下使用async正常的姿势:
fun main(...) { launch(Unconfined) { // 任务1会立即启动, 并且会在别的线程上并行执行 val deferred1 = async { requestDataAsync1() } // 上一个步骤只是启动了任务1, 并不会挂起当前协程 // 所以任务2也会立即启动, 也会在别的线程上并行执行 val deferred2 = async { requestDataAsync2() } // 先等待任务1结束(等了约1000ms), // 然后等待任务2, 由于它和任务1几乎同时启动的, 所以也很快完成了 println("data1=$deferred2.await(), data2=$deferred2.await()") } Thead.sleep(10000L) // 继续无视这个sleep}suspend fun requestDataAsync1(): String { delay(1000L) return "data1" }suspend fun requestDataAsync2(): String { delay(1000L) return "data2" }
运行结果很简单, 不用说了, 但是协程总耗时是多少呢, 约1000ms, 不是2000ms, 因为两个任务是并行运行的.
有一个问题: 假如任务2先于任务1完成, 结果是怎样的呢?
答案是: 任务2的结果会先保存在deferred2里, 当调用deferred2.await()时, 会立即返回, 不会引起协程挂起, 因为deferred2已经准备好了.
所以, suspend方法并不总是引起协程挂起, 只有其内部的数据未准备好时才会.
需要注意的是: await是suspend方法, 但async不是, 所以它才可以在协程外面调用, async只是启动了协程, async本身不会引起协程挂起, 传给async的lambda(也就是协程体)才可能引起协程挂起.
2.函数
2.1默认参数
函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量。
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { …… }
覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值:
open class A { open fun foo(i: Int = 10) { …… }}class B : A() { override fun foo(i: Int) { …… } // 不能有默认值}
如果一个默认参数在一个无默认值的参数之前,那么该默认值只能通过使用命名参数调用该函数来使用:
fun foo(bar: Int = 0, baz: Int) { /* …… */ }foo(baz = 1) // 使用默认值 bar = 0
不过如果最后一个 lambda 表达式参数从括号外传给函数函数调用,那么允许默认参数不传值:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* …… */ }foo(1) { println("hello") } // 使用默认值 baz = 1 foo { println("hello") } // 使用两个默认值 bar = 0 与 baz = 1
2.2 命名参数
可以在调用函数时使用命名的函数参数。当一个函数有大量的参数或默认参数时这会非常方便。
给定以下函数
fun reformat(str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') {……}
我们可以使用默认参数来调用它:
reformat(str)
然而,当使用非默认参数调用它时,该调用看起来就像:
reformat(str, true, true, false, '_')
使用命名参数我们可以使代码更具有可读性:
reformat(str, normalizeCase = true, upperCaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_')
并且如果我们不需要所有的参数:
reformat(str, wordSeparator = '_')
当一个函数调用混用位置参数与命名参数时,所有位置参数都要放在第一个命名参数之前。例如,允许调用 f(1, y = 2) 但不允许 f(x = 1, 2)。
可以通过使用星号操作符将可变数量参数(vararg) 以命名形式传入:
fun foo(vararg strings: String) { /* …… */ }foo(strings = *arrayOf("a", "b", "c"))foo(strings = "a") // 对于单个值不需要星号
3.其他知识点
3.1 componentX (多声明)
val f1 = Forecast(Date(), 27.5f, "Shinny")val (date, temperature, details) = f1//=======================// 上面的多声明会被编译成下面的代码val date = f1.component1()val temperature = f1.component2()val details = f1.copmponent3()// 映射对象的每一个属性到一个变量中,这就是 多声明。// object class 默认具有该属性。但普通 class 想要具有这种属性,需要这样做:class person(val name: String, val age: Int) { operator fun component1(): String { return name } operator fun component2(): Int { return age }}
val 必须有: 用来保存在 component1 和 component2 中返回构造函数传进来的参数的。
operator 暂时还不明真相,IDE 提示的。 操作符重载,函数名为操作符名(即系统默认的关键词,此处为 component1,component2).当使用该操作时,自己重写的操作会覆盖系统默认的操作。
// 常见用法:该特性功能强大,可以极大的简化代码量。 如 map 中的扩展函数实现,允许在迭代时使用 key valuefor ((key, value) in map) { Log.d("map","key:$key, value:$value")}
3.2 inline (内联函数)
内联函数与普通的函数有点不同。一个内联函数会在编译的时候被替换掉,而不是真正的方法调用。这在译写情况下可以减少内存分配和运行时开销。例如,有一函数只接收一个函数作为它的参数。如果是普通函数,内部会创建一个含有那个函数的对象。而内联函数会把我们调用这个函数的地方替换掉,所以它不需要为此生成一个内部的对象。
// 例一、创建代码块只提供 Lollipop 或更高版本来执行inline fun supportsLollipop(code: () -> Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { code() }}// usagesupportsLollipop { window.setStatusBarColor(Color.BLACK)}
3.3 Application 单例化和属性的 Delegated (by)
class App : Application() { companion object { private var instance: Application? = null fun instance() = instance!! } override fun onCreate() { super.onCreate() instance = this } }
我们可能需要一个属性具有一些相同的行为,使用 lazy 或 observable 可以被很有趣的实现重用,而不是一次又一次的去声明那些相同的代码。kotlin 提供了一个委托属性到一个类的方法。这就是委托属性。
class Delegate<T> : ReadWriteProperty<Any?, T> { fun getValue(thisRef: Any?, property: KProperty<*>): T { return ... } fun setValue(thisRef: Any?,property: KProperty<*>, value: T) {...} // 如果该属性是不可修改(val), 就会只有一个 getValue 函数 }
3.4 Not Null
场景1:需要在某些地方初始化该属性,但不能在构造函数中确定,或不能在构造函数中做任何事。
场景2:在 Activity fragment service receivers…中,一个非抽象的属性在构造函数执行之前需要被赋值。
解决方案1:使用可 null 类型并且赋值为 null,直到真正去赋值。但是,在使用时就需要不停的进行 not null 判断。
解决方案2:使用 notnull 委托。含有一个可 null 的变量并会在设置该属性时分配一个真实的值。如果该值在被获取之前没有被分配,它就会抛出一个异常。
class App : Application() { companion object { var instance: App by Delegates.notnull() } override fun onCreate() { super.onCreate() instance = this }}
3.5 从 Map 中映射值
另一种委托方式,属性的值会从一个map中获取 value,属性的名字对应这个map 中的 key。
import kotlin.properties.getValueclass Configuration(map: Map<String,Any?>) { val width: Int by map val height: Int by map val dp: Int by map val deviceName: String by map}// usageconf = Configuration(mapof( "width" to 1080, "height" to 720, "dp" to 240, "deviceName" to "myDecive"))
3.6 custom delegate
自定义委托需要实现 ReadOonlyProperty / ReadWriteProperty 两个类,具体取决于被委托的对象是 val 还是 var。
// step1private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> { private var value: T? = null override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("${desc.name not initialized}") } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = if (this.value == null) value else throw IllegalStateException("${desc.name} already initialized") }}// step2: usageobject DelegatesExt { fun notNullSingleValue<T>(): ReadWriteProperty<Any?, T> = NotNullSingleValueVar()}
3.7 重新实现 Application 单例
class App : Application() { companion object { var instance: App by Delegates.notNull() } override fun onCreate() { super.onCreate() instance = this }} // 此时可以在 app 的任何地方修改这个值,因为**如果使用 Delegates.notNull(), //属性必须是 var 的。可以使用刚刚创建的委托,只能修改该值一次companion object { var instance: App by DeleagesExt.notNullSingleValue()
4.使用协程别导错包
import org.jetbrains.anko.coroutines.experimental.Refimport org.jetbrains.anko.coroutines.experimental.asReferenceimplementation "org.jetbrains.anko:anko-coroutines:$anko_version"fun loadAndShowData() {// Ref<T> uses the WeakReference under the hoodval ref: Ref<MainActivity> = this.asReference()async(CommonPool) {val data = getData()// Use ref() instead of this@MyActivitylaunch(UI) { ref().asyncOverlay() }}}fun getData(): Data { ... } fun showData(data: Data) { ... } async(UI) { val data: Deferred<Data> = bg { // Runs in background getData() } // This code is executed on the UI thread showData(data.await()) }
- Kotlin中一些知识点学习
- Kotlin学习中触碰到的知识点
- kotlin学习过程中的一些常识(持续更新中)
- Android学习中一些零散的知识点
- Kotlin学习中
- 一些知识点学习
- c#中一些知识点
- Integer中一些知识点
- 【java emf】学习EMF过程中一些知识点
- 一些Java学习中应谨记的知识点
- 研究学习Kotlin的一些方法
- kotlin学习记录以及一些疑问
- 研究学习Kotlin的一些方法
- 最近学习的一些知识点
- Uinity学习的一些知识点
- caffe--学习中的一些知识点
- [Android-Kotlin学习日志]AndroidStudio中Kotlin环境搭建
- Kotlin学习一 Android Studio 中安装 Kotlin Plugin
- 随想,随笔
- 微信小程序:新功能WXS解读(2017.08.30新增)
- C++初始化字符串
- SpringBoot系列2—整合Mybatis-plus
- coreseek和xunsearch的使用
- Kotlin中一些知识点学习
- C++学习笔记(3)-重载与多态
- docker基础命令
- Android 简单的SharedPreferences轻量型存储方式
- centos7 打开指定的端口
- 情感分析
- 23. Merge k Sorted Lists
- mysql中的tinyint,smallint,int,bigint
- struts+hibernate工作原理和简单工程创建步骤(附工程下载地址)