3.1 object
用object修饰的类,实际上是单例类,在Kotlin中调用时是类名加方法直接使用.
3.2 companion object
用companion object修饰的方法也能通过类名加.直接调用,但是这时通过伴生对象实现的.在原有类中生成一个伴生类,Kotlin会保证这个伴生类只有一个对象.
3.3 @JvmStatic注解
给单例类(object)和伴生对象的方法加@JvmStatic注解,这时编译器会将这些方法编译成真正的静态方法.
3.4 顶层方法
Kotlin会将所有的顶层方法全部编译成静态方法.
密封类用来表示受限的类继承结构: 当一个值为有限集中的类型,而不能有任何其他类型时.
就比如说,应用内的广告源只有3种,那么我们就可以使用密封类来定义.然后在when判断的时候就非常方便,如果缺少了一个判断,则会编译报错.
sealed class Result
class Success(val msg: String) : Result()
class Failure(val error: Exception) : Result()
fun getResultMsg(result: Result) = when (result) {
is Success -> result.msg
is Failure -> “Error is ${result.error.message}”
}
class TestExpand(private val name: String, val age: Int)
fun TestExpand.expandMethod() {
//扩展函数不能访问原有类的私有属性
println(age)
}
-
不修改某个类源码的情况下,动态地添加新的函数.
className.
-
扩展函数不能访问原有类的私有属性
-
底层实际上是用写了个静态函数来实现的,将类的实例传入这个静态函数,然后搞一些操作
如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数
6.1 高阶函数定义
前面是参数,后面是返回值类型(String,int) -> Unit
.
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
return operation(num1, num2)
}
fun main() {
val result1 = num1AndNum2(1, 2) { n1, n2 ->
n1 + n2
}
val result2 = num1AndNum2(2, 5) { n1, n2 ->
n1 - n2
}
println(result1)
println(result2)
}
高阶函数的完整语法:
//给StringBuilder增加扩展方法build,需要传入一个高阶函数,这个高阶函数只作用于StringBuilder,所以需要写成StringBuilder.() -> Unit,这样可以在里面访问到StringBuilder的上下文
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
val result = StringBuilder().build {
append(“Start\n”)
for (s in list) {
append(s).append(“\n”)
}
append(“end”)
}
6.2 内联函数
其实默认情况下,高阶函数会被Kotlin编译器转换成实现了Function接口的匿名类,这其实会造成额外的内存和性能开销.
为了解决这个问题,Kotlin提供了Kotlin提供了内联函数,将使用Lambda表达式带来的运行时开销完全消除.
直接在定义函数的地方前面加上inline关键词即可.Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方.所以可以完全消除Lambda表达式所带来的运行时开销.
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
return operation(num1, num2)
}
7.1 泛型
- 给方法加泛型
fun method(param: T) : T {
return param;
}
- 给类加泛型
class MyClass {
fun method(param: T) : T {
return param;
}
}
7.2 委托
委托是一种设计模式,它的基本理念是: 操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理.
好处: 如果一个类,让大部分的方法实现都是调用辅助对象中的方法,而少部分的方法是自己实现的,这种情况,就是委托模式的意义所在.
- 类委托
kotlin支持类委托给另一个辅助对象,语法层面就支持,拥有辅助对象的所有功能,还能自己去实现或构建独有功能. 直接通过关键字by完成,简化自己去编写模板代码
//MySet中有所有Set接口中的功能,和HashSet保持一致, 并且isEmpty是自己实现的
class MySet(val helperSet: HashSet) : Set by helperSet {
fun hello() = println(“hello”)
//演示,
override fun isEmpty(): Boolean {
return true
}
}
- 属性委托
将一个属性的具体实现委托给另一个类去完成.
8.1 泛型实化
Kotlin有内联函数,而内联函数是提替换到被调用的地方,所以不存在泛型擦除(泛型对于类型的约束只在编译期存在)问题.
泛型实化书写,首先得是内联函数,其次得加reified关键字,然后可以在函数内获取当前指定泛型的实际类型
inline fun getGenericType() = T::class.java
val result1 = getGenericType()
val result2 = getGenericType()
线程需要依靠操作系统的调度才能实现不同线程之间的切换.而协程可以在编程语言的层面实现不同协程之间的切换,从而大大提升并发编程的运行效率.
协程允许在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制,和操作系统无关.
-
Kotlin for Java的协程并不属于广义的协程,而是一个线程框架
-
可以用看起来同步的代码写出实质上异步的操作
-
suspend并不是拿来切线程的,而是用来做标记和提醒的,调用的时候需要放协程里面才行.
-
协程是怎么切线程的:看Kotlin编译成的class对应的Java代码发现,其实就是将代码分块,某一块代码执行在1个线程,另一块代码执行在另一个线程中.在编译的时候就搞了这样的操作.
9.1 创建协程作用域
GlobalScope.launch可以创建一个协程的作用域,传递给launch函数的代码块(Lambda表达式)就是在协程中运行的.
GlobalScope.launch {
println(“codes run is coroutine scope”)
//这里不是主线程 DefaultDispatcher-worker-1
println(Thread.currentThread().name)
}
GlobalScope.launch创建的是顶层协程,当应用程序结束时也会跟着结束.
可以在协程中加入delay()函数,delay()函数可以让当前协程延迟指定时间后再运行.
delay是非阻塞式的挂起函数,它只会挂起当前协程.而Thread.sleep()会阻塞当前的线程,该线程的所有协程都会被阻塞.
GlobalScope.launch {
println(“codes run is coroutine scope”)
//这里不是主线程
println(Thread.currentThread().name)
delay(1000)
println(“延迟之后的输出”)
}
runBlocking也能创建一个协程的作用域,它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程.runBlocking函数通常只应该在测试环境下使用,在正式环境中容易产生性能上的问题.
9.2 创建多个协程
fun main() {
//创建多个协程
runBlocking {
launch {
println(“launch1 ${Thread.currentThread().name}”)
delay(1000)
println(“launch1 finished”)
}
launch {
println(“launch2 ${Thread.currentThread().name}”)
delay(1000)
println(“launch2 finished”)
}
}
}
这里的launch函数和刚才的GlobalScope.launch不一样,这个launch只能在协程的作用域下面调用,且会创建一个子协程.子协程的特点是如果外层作用域的协程结束了,那么该作用域下的所有子协程也会一同结束.
上面的输出如下:
launch1 main
launch2 main
launch1 finished
launch2 finished
日志是交叉打印的,很明显,这是并发执行的.但是线程却是相同的线程.这里由编程语言来决定如何在多个协程之间进行调度,让谁挂起,让谁运行.调度过程不需要操作系统参与,这使得协程并发效率出奇得高.
9.3 suspend 挂起
当需要将部分代码提取到一个单独的函数中,这个函数是没有协程作用域的.Kotlin提供一个suspend关键字,使用它可以将任意函数声明成挂起函数,挂起函数之间是可以互相调用的.
suspend只能声明挂起函数,而不能提供协程作用域,在里面调用launch(必须在协程作用域调用才行)是不得行的.要想有协程作用域,可以使用coroutineScope.
coroutineScope也是一个挂起函数,因此可以在其他任何挂起函数中调用.coroutineScope会继承外部的协程作用域并创建一个子作用域. 于是可以这样用:
suspend fun printDot() {
coroutineScope {
launch {
println(“.”)
delay(1000)
println(“延迟之后的输出”)
}
}
}
coroutineScope有点类似runBlocking,保证其作用域内的所有代码和子线程全部执行完之前,会一直阻塞当前协程.
但是runBlocking会阻塞当前线程,影响较大.而coroutineScope只会阻塞当前协程,不会影响其他协程,也不会影响其他线程.
可创建新的协程作用域:
-
GlobalScope.launch 可在任何地方调用
-
runBlocking 可在任何地方调用
-
lanuch
-
coruotineScope
9.4 更多的作用域构建器
runBlocking会阻塞线程,只能在测试环境下使用.而GlobalScope.launch是顶层协程,比如在Activity中使用来请求网络,还没请求回来的时候,Activity已关闭,这时需要手动管理(去取消)这个顶层协程,比较麻烦. 调用下面的代码会取消顶层协程.
val job = GlobalScope.launch { }
job.cancel()
但是实际项目中,一般会用CoroutineScope
val job = Job()
//返回的是CoroutineScope对象 这里是调用的CoroutineScope方法
val scope = CoroutineScope(job)
scope.launch {
}
所有使用CoroutineScope对象的launch创建的协程统统会被job所管理(都是在它的作用域下面).大大降低协程维护成本.
9.5 创建协程,并获取其执行结果
使用async函数,就可以获取协程的执行结果.它会创建一个子协程,并返回Deferred对象,然后我们调用其await方法即可知道结果.下面是简单计算一下5+5
runBlocking {
val result = async {
delay(100)
5 + 5
}.await()
println(result)
}
注意,调用await方法之后会阻塞当前协程,直到子协程拿到结果,才会执行后面的代码(对应上面是println语句).为了提高效率,可以先拿到返回Deferred对象,最后需要结果的时候才调用await方法
runBlocking {
val start = System.currentTimeMillis()
val deferred1 = async {
delay(1000)
5 + 5
}
val deferred2 = async {
delay(1000)
6 + 6
}
println(“结果是 ${deferred1.await() + deferred2.await()}”)
val end = System.currentTimeMillis()
println(“花费时间: ${end - start}”)
}
9.6 withContext
withContext大致是async函数的简化版,它是一个挂起函数,返回结果是withContext函数体内最后一行代码.相当于val result = async{5+5}.await()
runBlocking {
val result = withContext(Dispatchers.Default) {
5 + 5
}
println(result)
}
调用withContext函数后,函数体内的代码会被立即执行,同时需要指定一个线程参数.这个参数有如下几个值:
-
Dispatchers.Default 会开启子线程,并使用一种较低并发的线程策略.适合计算密集型任务.
-
Dispatchers.IO 会开启子线程,并使用一种较高并发的线程策略.网络请求比较合适
-
Dispatchers.Main 不会开启子线程,而是在Android主线程执行代码.
9.7 使用协程简化回调
suspendCoroutine 函数可以将当前协程立即挂起,然后在一个普通的线程执行lambda表达式中的代码.Lambda表达式的参数是一个Continuation参数,调用它的resume方法或resumeWithException可以让协程恢复执行.
来看一段代码:
suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
continuation.resume(response)
}
override fun onError(e: Exception) {
continuation.resumeWithException(e)
}
})
}
}
GlobalScope.launch {
val response = request(“https://www.baidu.com/”)
Log.d(“xfhy”, “网络请求结果 : $response”)
}
将网络请求的代码用suspendCoroutine包装一下,免得每次去手动生成一个匿名类,然后在里面拿到结果的时候调用continuation的resume方法将结果返回,这样在外面即可拿到结果. 使用request方法请求网络,只需要写一句代码即可.上面为了实例,没有加try…catch.
Linux内核
Android平台的基础是Linux内核.
结尾
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
手动生成一个匿名类,然后在里面拿到结果的时候调用continuation的resume方法将结果返回,这样在外面即可拿到结果. 使用request方法请求网络,只需要写一句代码即可.上面为了实例,没有加try…catch.
Linux内核
Android平台的基础是Linux内核.
结尾
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~
[外链图片转存中…(img-5cQNoImW-1715352779268)]
[外链图片转存中…(img-WES3i6Ii-1715352779269)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!