前言:大部分人可能写了好几年kotlin,各种协程,高阶函数都有使用过,顶层函数也涉及到过,遇到不会的找找资料,参考别人的写法,完成功能。最近抽时间针对kotlin学习了一下,对kotllin的理解又深入了一些。
个人认为真正需要开发者关注的主要还是下面几个点:
1.高阶函数
2.扩展函数
3.kotlin泛型
4.协程相关
1.高阶函数
一句话概括:函数中有lambda就是属于高阶函数, 函数的函数就是高阶函数.
下面举几个常见的例子:
// 高阶函数定义
fun test(number: Int, lambda : (Int) -> String) : String = lambda.invoke(number)
//函数调用
var r : String = test(777) { /*it: Int ->*/
"数据是:$it"
}
上面是最简单的一个高阶函数, lambda表达式最后一行是返回值为String类型。
需要特别说明的是高阶函数的右边是函数的声明,实现是调用时候的{ } 中的内容
调用时候,会执行lambd表达式 因为只有一个参数,直接可以用it代替。
定义test方法,返回一个匿名方法,传入两个int值,返回一个字符串,注意调用时候是带两个括号
val test = fun()
: (Int, Int) -> String
= {n1, n2 -> "两个数相加:${n1 + n2}" }
// 调用方法
test()(1000, 1000)
用在登录场景: 可以看下注释内容
,常规我们可能要写一个接口定义成功失败方法传进去 回调。用下面高阶函数在这种场景其实可以不用定义。只是举例说明场景,实际项目看具体情况使用
/**
* 高阶函数定义,传入名字,密码,回调函数 这里的回调函数是声明:声明一个回调传入String,不需要返回值。实际项目string可以是某个对象
*/
/高阶函数定义,传入名字,密码,回调函数 这里的回调函数是声明:声明一个回调传入String,不需要返回值。实际项目string可以是某个对象/
private fun loginEngine(userName: String, userPwd: String, responseResult: (String) -> Unit) {
// mock网络请求
if (userName == "詹姆斯" && userPwd == "123456") {
responseResult("{\"code\"\"200\", \"data\":\"XXXXX\"}")
} else {
responseResult("{{\"code\"\"404\"}")
}
}
// 调用
loginEngine("詹姆斯", "123456") {
if (it.contains("200")) println("最终登录的结果是:登录成功") else println("最终登录的结果是:登录失败!!")
}
2.高阶函数+扩展函数
写过一段时间kt的应该都用过let、apply、run、这几个API,天天用,很多人一直都不懂原理。网上也有专门讲这几个扩展函数的:https://mp.weixin.qq.com/s/kqUA_t2C_cT-5lVtuBdU0Q 可以看看。分析下run方法源码如下:
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
//let源码
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
1.方法使用了inline修饰,内联就是会把代码拷贝到调用地方,不用单独多创建一个对象出来
2. 入参T 使用泛型,R代表返回值,注意看T.run 代表给任意类型扩展一个run方法
3.lambda的输入声明的是T.() -> R 代表输入是T的匿名函数 输出是R本身,最后返回lambda表达式
第二点提到使用T.() 就说明调用的地方 { } 里面是this。 如果是(T)- > R 在调用的 地方{ }里面就是it 如上图。AS工具里面也可以看到如下:
参照源码,自定义一个如下:
fun <T> T.自定义运行方法(block: T.(Float) -> Boolean) {
// this == T本身 == 调用者本身
block(11111f) // 调用Lambda
}
//调用:
"测试".自定义run {
println("谁调用 $this 传入的价格是:$it ")
true }
为了更好理解,我直接命名“自定义运行方法”为方法名字,也就是对任意类型,扩展一个“自定义运行方法”。方法声明为一个lambda表达式,在调用时候 { } 里面入参是一个浮点型,it可以获取到,返回是boolean类型。看下面图黄笔划线地方。this代表谁调用的,it是lambda表达式的输入 里面我直接写死了1111f.
扩展函数
fun <T> T.abc() {
// this == T本身 == 调用者本身
println("abc我是:$this")
}
比如上面代码,给T扩展一个函数,后面使用任意类型就可以.abc方法打印本身。也就是说 我们可以为OKHttp、View、Activity等很多方法扩展一些方法,而不改原始的类,比如定义如下方法,就可以String.toast()了:
fun Any.toast() {
// Any == this
println("任意类型调用,你的值是:$this")
}
3.kotlin泛型
直接上问题,在java中:
List<Object> aa = new ArrayList();
List<String > bb = new ArrayList();
请问 aa = bb 是否可以
结论就是List<父> aa = new ArrayList<子>(); 只能获取,不能往里面加,因为加一些其它类型就有问题。下面直接上图更清晰:
那看下另外 ?super 场景呢
? extends XX == out 代表只能往外取,程序员视角是只能
? super XX == in 不知道爹是谁,只能 进去改 程序员视角是只能
4.协程
定义:
由kotlin官方提供的一套线程框架API , 借助KT语言优势,用看起来同步方式写出异步代码, (同一个代码块可多次切换线程)
优点:
可以轻松实现两个并行请求,比如获取用户ID 再登陆,之前可能是嵌套。要等第一个请求成功后再发起第二个请求。执行效率慢一倍,现在用协程直接可以并行使用async 实现
launch函数:
意思是创建一个新的协程运行在指定的线程上。
suspend解释:
suspend 其实是一个提醒,是函数创建者对调用者提醒,告诉要在携程里面调用 ,真正挂起是靠携程里面实际代码 比如withContext切线程
协程中suspend
只是一个标记 ,挂起,挂起的对象是协程 ,从当前正在执行的线程挂起,就是会脱离当前线程。比如在主线程运行 执行到一个挂起函数,挂起函数用withContext切换到IO线程执行,执行完后会自动切换回原来的主线程继续执行,切回来的动作叫做 resume。
suspend 编译后如下图, continuation 其实就是callback 单词意识就是持续继续 保证后面剩余代码恢复工作
什么又是非阻塞试挂起
不卡线程 就叫非阻塞式挂起, 网上说的 协程是非阻塞 线程是阻塞有个前提条件,单线程场景下 。 耗时操作 分两种:1种是IO操作一种CPU计算 而网络就属于IO,性能瓶颈是IO,和网络交互 所以线程被网络交互阻塞 但阻塞没办法避免。 协程没有比线程更高级,只是自动切回来/协程的 非阻塞式挂起只是用阻塞的方式写出非阻塞的代码而已。并没有更高效的 就是上层框架。携程的本质还是线程。包括官网的文档,也有误导,对比Thread sleep.
状态机模式
里面有个枚举。挂起 没挂起,resume, 主要实现是ContinuationImpl类,大概会初始化变量,把主线程的变量copy出来, 主要为了恢复 实力滑 invokeSuspend 函数 里面一个循环,判断状态机 执行请求好事任务,执行完成后
协程关键字
做两个小测试,看下是否能回答得上,下面打印hello word 大部分人都能回答上,在看再下面一个代码估计大部分人都会回答错:
fun main() = runBlocking { // this: CoroutineScope
launch { // 在 runBlocking 的作用范围内启动新的协程
delay(1000L)
println("World!")
}
println("Hello,")
}
下面代码 1、2、3、4打印顺序如何:
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking 1")
}
coroutineScope { // 创建新的协程作用范围
launch {
delay(500L)
println("Task from nested launch 2")
}
delay(100L)
println("Task from coroutine scope 3") // 在嵌套的 launch 之前, 这一行会打印
}
println("Coroutine scope is over 4") // 直到嵌套的 launch 运行结束后, 这一行才会打印 }
}
截图如下,是不是回答错了,打印的是3-1-2-4, 因为coroutineScope
关键字:
协程关键字
CoroutineScope 协程的作用域
CoroutineDispatcher 协程的调度器
CoroutineContext 协程上下文
异常捕获的注意事项
1.协程里面的异常捕获,需要在里面单独处理
fun main(args: Array<String>) {
try {
GlobalScope.launch {
println(test())
}
} catch (e:Exception) {
}
Thread.sleep(100)
}
suspend fun test(): String = withContext(Dispatchers.Default) {
throw RuntimeException("this is exception")
"测试..."
}
个人理解,suspend函数会从当前协程线程上切走,也就是test()运行在协程的子协程上,但是捕获异常还是捕获的当前协程,所以捕获不到。 使用如下方式捕获:
GlobalScope.launch {
try {
println(doSomething())
} catch (e:Exception) {
}
}