Java教程

Java面试题目,Kotlin(3)-协程和操作符重载面试必问!

本文主要是介绍Java面试题目,Kotlin(3)-协程和操作符重载面试必问!,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

以一个java老鸟的角度,如何去看 kotlin。 Java源代码应该如何用Kotlin重构。 如何正确学习kotlin并且应用到实际开发中。本文将会探究。

本文分两大块,重难点和潜规则。

重难点:Kotlin中可以独立出来讲解的大块知识点。提供单独Demo。这部分大多数是Kotlin开创的新概念(相比于Java)。

潜规则:Kotlin是谷歌用来替换Java的,它和java百分百完全兼容,但是实际上java转成kotlin之后,需要我们手动修改很多东西,甚至某些部分必须打散重构来达到最优编码。其中,kotlin的某些特性和java不同,甚至完全反转。这部分知识点比较零碎,单独Demo不方便提供,就以小例子的形式来写。

正文大纲

  • 重难点

    • lambda以及操作符
    • 高阶函数以及操作符
    • Kotlin泛型
    • 集合操作
    • 协程
    • 操作符重载
  • 潜规则

    • Kotlin 文件和类不存在一对一关系

    • 共生体

    • 继承

    • 修饰符

    • 空指针问题

正文

重难点

协程

想了很久,关于协程的内容,在官网上确实有很多内容,基础知识概念,基本使用,以及 流操作,通道,异常处理,并发处理等,不方便在这里展开。具体的参照:Kotlin中文网

这里具体去学习。本章节,只总结一下近期查阅资料并经本人验证的知识点。

概念

英文 coroutines : /,kəuru:'ti:n/ 意: 协同程序。 简称协程。

Kotlin提出协程概念,是为了简化异步编程,让开发者更容易控制函数的执行流程。

协程和线程的联系和区别

在操作系统OS中,进程资源分配的最小单位线程任务调度的最小单位。而协程则是处在线程内部的**“微线程”,或者说轻量级线程**。 由于线程在OS中是稀缺资源,所有OS平台的线程数量都是有上限的,平时编程我们会用线程池来管理线程数量,熟悉线程池的同学应该知道,线程池管理线程,无论是核心线程还是非核心线程,都不会随意去创建,除非迫不得已。

线程解决异步问题

  • 多线程同步编程可以通过加锁解决数据的线程安全问题,但是加锁会降低程序执行效率,并且锁多了,会有死锁隐患
  • 线程的状态转换完全由内核控制,程序员开发者无法干涉
  • 线程的是稀缺资源,不能随意创建,使用线程解决异步问题,线程的初始化,上下文切换(CPU轮转),线程状态切换(sleep,yield…), 变量加锁操作(synchronized关键字),都会使得线程的使用代价比较大

协程解决异步问题

  • 协程是运行在线程之上的优化产物,或称“微线程”。协程依赖线程运行,复杂的底层逻辑被封装在库内,使用时无需关心所处线程状态
  • 使用协程,开发者可以自己控制协程的状态(suspend挂起,resume恢复),而不会像线程那样依赖底层调度,时间片争夺。
  • 一个线程可以跑多个协程,一个协程也可以分段在多个线程上执行
  • 协程 是 非阻塞的,当前协程挂起之后,所在线程资源并不会浪费,它会去执行其他协程(如果有的话)
  • 协程 相对于线程这种OS中的稀缺资源,它是极其轻量级的,就算你开一百万个协程,对于系统的压力也不会像大量线程那样大(别说一百万个,linux系统的线程数量上线是1000,超过这个值系统就无法正常运行).
  • 总之一句话 : 协程的出现,让程序开发者对程序逻辑的掌控提升到了一个新的境界,想象一下,一个函数正在执行,你想让他在某个时刻暂停,然后在另一个时刻继续。利用线程恐怕很难做到。协程中,轻而易举。

基本使用

module级别的build.gradle 中 引入库依赖,推荐使用最新版(目前稳定版是1.3.3)

dependencies {
    //...
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" // 如果你需要用到协程调度器Dispatchers的话,必须加上这个依赖
}
协程的创建

创建协程有多种方式,全局/独立,同步/异步,并且可以指定 “协程调度器”

  • runBlocking

    fun main() {
        runBlocking {
            println("这是runBlocking协程...")
        }
    }
    
  • launch

    fun main() {
        runBlocking {
            println("这是runBlocking协程...")
            launch {
                println("这是runBlocking内部的runBlocking协程...")
                delay(2000)
                println("延迟了2000MS之后,再打印")
            }
        }
    }
    
  • GlobalScope.launch

    fun main() {
        println("协程,相对于主线程来说,都是异步的,也就是说,你在这里插入协程逻辑,主线程的逻辑并不会被阻塞")
        GlobalScope.launch {
            delay(3000)
            println("GlobalScope.launch 创建协程 ")
        }
        println("主线程继续")
        Thread.sleep(5000)
    }
    
  • Global.async

    fun main() {
        runBlocking {
            val async: Deferred<String> = GlobalScope.async {
                println("这是一个异步协程,他将返回一个Deferred")
                delay(2000)
                "异步任务返回值"
            }
            println("主线程继续:" + async.await())
        }
    
        Thread.sleep(5000)
    }
    
“骚操作”

关心协程的人一般都会十分关注它到底能给我们异步编程带来怎样的便利。这里总结几个不用协程实现起来很麻烦的骚操作

  • 如果有一个函数,它的返回值需要等到多个耗时的异步任务都执行完毕返回之后,组合所有任务的返回值作为 最终返回值

    fun test6(): String = runBlocking {
        var finalRes = ""
        coroutineScope {
            launch {
                delay(1000)
                finalRes = finalRes.plus("1")
            }
            launch {
                delay(2000)
                finalRes = finalRes.plus("2")
            }
    
            launch {
                delay(3000)
                finalRes = finalRes.plus("3")
            }
        }
        finalRes
    }
    
    fun main() {
        val test6 = test6()
        println("最终返回值是: $test6")
    }
    

    最终返回结果为(延迟3秒之后打印):

    最终返回值是: 123
    
  • 如果有一个函数,需要顺序执行多个网络请求,并且后一个请求依赖前一个请求的执行结果

    import kotlinx.coroutines.*
    
    suspend fun getToken(): String {
        for (i in 0..10) {
            println("异步请求正在执行:getToken :$i")
            delay(100)
        }
        return "ask"
    }
    
    suspend fun getResponse(token: String): String {
        for (i in 0..10) {
            println("异步请求正在执行:getResponse :$token $i")
            delay(100)
        }
    
        return "response"
    }
    
    fun setText(response: String) {
        println("setText 执行,时间:  ${System.currentTimeMillis()}")
    }
    
    fun main() {
        GlobalScope.launch(Dispatchers.Unconfined) {
            var token = GlobalScope.async(Dispatchers.Unconfined) {
                return@async getToken()
            }.await() // 创建异步任务,并且 阻塞执行 await 是阻塞执行取得结果
    
            var response = GlobalScope.async(Dispatchers.Unconfined) {
                return@async getResponse(token)
            }.await() // 创建异步任务,并且立即执行
    
            setText(response)
        }
    
        Thread.sleep(20000)
    }
    

    执行结果:

    异步请求正在执行:getToken :0
    异步请求正在执行:getToken :1
    异步请求正在执行:getToken :2
    异步请求正在执行:getToken :3
    异步请求正在执行:getToken :4
    异步请求正在执行:getToken :5
    异步请求正在执行:getToken :6
    异步请求正在执行:getToken :7
    异步请求正在执行:getToken :8
    异步请求正在执行:getToken :9
    异步请求正在执行:getToken :10
    异步请求正在执行:getResponse :ask 0
    异步请求正在执行:getResponse :ask 1
    异步请求正在执行:getResponse :ask 2
    异步请求正在执行:getResponse :ask 3
    异步请求正在执行:getResponse :ask 4
    异步请求正在执行:getResponse :ask 5
    异步请求正在执行:getResponse :ask 6
    异步请求正在执行:getResponse :ask 7
    异步请求正在执行:getResponse :ask 8
    异步请求正在执行:getResponse :ask 9
    异步请求正在执行:getResponse :ask 10
    setText 执行,时间:  1578904290520
    
  • 当前正在执行一项异步任务,但是你突然不想要它执行了,随时可以取消

    fun main() {
        // 协程任务
        val job = GlobalScope.launch(Dispatchers.IO) {
            for (i in 0..100){// 每次挂起100MS,100次也就是10秒
                println("协程正在执行 $i")
                delay(100)
            }
        }
    
        // 但是我在1秒之后就取消协程
        Thread.sleep(1000)
        job?.cancel()
        println( "btn_right 结束协程")
    }
    

    执行结果(本该执行100轮的打印,只持续了10轮):

    协程正在执行 0
    协程正在执行 1
    协程正在执行 2
    协程正在执行 3
    协程正在执行 4
    协程正在执行 5
    协程正在执行 6
    协程正在执行 7
    协程正在执行 8
    协程正在执行 9
    btn_right 结束协程
    
    Process finished with exit code 0
    
  • 如果你想让一个任务最多执行3秒,超过3秒则自动取消

    import kotlinx.coroutines.*
    
    
    fun main() = runBlocking {
        println("限时任务中结果是:" + getResFromTimeoutTask())
    }
    
    suspend fun getResFromTimeoutTask(): String? {
        // 忘了,它会保证内部的协程代码都执行完毕,所以不能这么写
        return withTimeoutOrNull(1300) {
            for (i in 0..10) {
                println("I'm sleeping $i ...")
                delay(500)
            }
            "执行结束"
        }
    }
    

    执行结果

    I'm sleeping 0 ...
    I'm sleeping 1 ...
    I'm sleeping 2 ...
    限时任务中结果是:null
    
    Process finished with exit code 0
    

总结

协程作为kotlin 区别于java的新概念,它的出现是为了解决java不好解决的问题,比如层层回调导致代码臃肿,比如 异步任务执行流程不好操控等。本章节篇幅有限,无法展开说明,但是对于新手而言,看完本章应该能对协程的作用有一个大概的认知。本人也是初步研究,后续有更深入的了解之后,再进行专文讲解吧。


操作符重载

概念

说人话,像是一元操作符 ++自加,二元操作符 +相加 ,默认只支持数字类型,比如Int. 但是通过操作符的重载,我们可以让任意类 都能 ++自加,且返回一个想要的对象。操作符执行的逻辑,完全看我们如何去设计。

分类

按元素级别

  • 一元

    表达式对应函数
    +aa.unaryPlus()
    -aa.unaryMinus()
    !aa.not()
    a++a.inc()
    a–a.dec()
  • 二元

    表达式对应函数
    a+ba.plus(b)
    a-ba.minus(b)
    a*ba.times(b)
    a/ba.div(b)
    a%ba.rem(b)
    a…ba.range(b)
    a in bb.contains(a)
    a !in b!b.contains(a)
    a[i]a.get(i)
    a[i,j]a.get(i,j)
    a[i_1,…,i_n]a.get(i_1,…,i_n)
    a[i]=ba.set(i,b)
    a[i,j]=ba.set(i,j,b)
    a[i_1,…,i_n]=ba.set(i_1,…,i_j,b)
    a()a.invoke()
    a(i)a.invoke(i)
    a(i,j)a.invoke(i,j)
    a(i_1,…,i_n)a.invoke(i_1,…,i_n)
    a+=ba.plusAssign(b)
    a-=ba.minusAssign(b)
    a*=ba.timesAssign(b)
    a/=ba.divAssign(b)
    a%=ba.modAssign(b)
    a > ba.compareTo(b)>0
    a < ba.compareTo(b)<0
    a>=ba.compareTo(b)>=0
    a<=ba.compareTo(b)<=0

按实现方式

  • 成员函数

  • 扩展函数

栗子

看到上面的一大堆,肯定有点懵,看个例子解决疑问。上面我用两种维度来对操作符重载进行了分类,那么,先试试:成员函数的方式来重载一个一元操作符

class A(i: Int, j: Int) {
    var i: Int = i
    var j: Int = j
    /**
     * 重载++操作
     */
    operator fun inc(): A {
        return A(i++, j++)
    }
    override fun toString(): String {
        return "[i=$i , j=$j]"
    }
}

如上代码,注意看:

 operator fun inc(): A {
        return A(i++, j++)
 }

Kotlin的操作符重载和 c++,dart语言内的操作符重载写法完全不同,它不再是直接把操作符放到了 重写的过程中,而是每一种支持重载的操作符都有一个对应的 函数名

正如:上表格中的 a++ 操作符,对应的函数就是 a.inc()

调用的时候:

fun main() {
    var a = A(1, 2)
    println("a:$a")
    println("a++:${a++}")
}

打印结果:

a:[i=1 , j=2]
a++:[i=2 , j=3]

再看一个二元运算符重载的栗子,这次我们不用成员函数,而是用扩展函数:

class A(i: Int, j: Int) {
    var i: Int = i
    var j: Int = j
    override fun toString(): String {
        return "[i=$i , j=$j]"
    }
}
/**
* 重载A类的 x+y 操作
*/
operator fun A.plus(a: A): A {
    return A(this.i + a.i, this.j + a.j)
}
fun main() {
    val x = A(1,1)
    val y = A(2,2)
    println(x+y)
}

这里演示的是 A类的 x+y 操作符重载。细节应该不用多说。

打印结果:

[i=3 , j=3]

再来一个较为复杂的栗子, 重载 a[i]

/**
 * 比如,B类中有一个成员,list,我想重载操作符,直接取到list中的元素
 */
class B {
    val list: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
}
//a[i]
operator fun B.get(i: Int): Int {
    return list[i]
}
fun main() {
    val b = B()
    println("${b[2]}")
}

打印结果:

3

最后一个栗子:a > b,对应函数为:a.compare(b)

/**
 * 学生class
 */
data class Student(val math: Int = 0, val chinese: Int = 0, val english: Int = 0)

fun Student.toString():String{
    return "[math:${math} chinese:${chinese} english:${english}]"
}
fun Student.totalScore(): Int {
    return math + chinese + english
}


/**
 * 比如,我们要直接比较两个学生的总分
 */
operator fun Student.compareTo(s: Student): Int {
    return this.totalScore() - s.totalScore()//比较2个学生的总分
}

fun main() {
    val s1 = Student(math = 50, chinese = 90, english = 100)
    val s2 = Student(math = 80, chinese = 70, english = 60)

    println("s1:${s1}")
    println("s2:${s2}")
    //比如存在这两个学生,我要知道他们的总分谁高谁低
    println("学生s1,s2的总分:${if(s1 > s2) "s1比较高" else "s2比较高" }")

}

打印结果:

s1:Student(math=50, chinese=90, english=100)
s2:Student(math=80, chinese=70, english=60)
学生s1,s2的总分:s1比较高

总结

通过以上几个栗子,应该能看出,kotlin的操作符重载,编码和使用都十分简单。重载之前需要确定两件事

  • 根据业务需求,确定要重载的是哪一个操作符,虽然操作符的最终执行逻辑完全由我们自定义,但是我们重载操作符的目的是为了 让使用者更简单的理解业务代码,所以,要选择原本意义更加符合业务需求的操作符。Kotlin支持的操作符在上述表格中都列举出来了,支持大部分一元二元操作符,但是二元中不支持===的重载,不支持三元操作符bool?a:b这种 。
  • 确定重载函数的 入参类型个数,以及返回值类型,并且编写操作符的执行逻辑。

Hi~ o(* ̄▽ ̄ *)ブ

疫情无情,人有情!

####小编宅家期间整理了约 300G 关于BATJ大厂的面试资料

需要的小伙伴都可以转发+私信回复“1” 免费领取!

2021年Java中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

部分内容:

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注后,点击这里免费获取!

、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

[外链图片转存中…(img-acxZuuZc-1623419626087)]

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

[外链图片转存中…(img-rBZyYK8o-1623419626088)]

部分内容:

[外链图片转存中…(img-IZOTKdIC-1623419626089)]

[外链图片转存中…(img-EAHpdn6P-1623419626090)]

[外链图片转存中…(img-I5AMZ0qT-1623419626091)]

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注后,点击这里免费获取!

这篇关于Java面试题目,Kotlin(3)-协程和操作符重载面试必问!的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!