网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
fun returnFunction():() -> Long{
return {System.currentTimeMills()}
}
高阶函数的调用
高阶函数的一个重要特性就是可以让函数类型参数决定函数的执行逻辑,就是说相同返回类型但执行逻辑不同的函数作为参数传入时,返回结果是不一样的。下面我们定义一个函数看一下:
//定义一个所谓的高级函数 函数参数block是两个参数为Int类型的函数变量,返回值是Int
fun exampleFun(a: Int, b: Int, block: (Int, Int) -> Int): Int {
return block(a, b)
}
//定义两个返回类型一样 但执行逻辑不一样的函数
fun aFun(n:Int,m:Int):Int{
return m*n
}
fun bFun(n:Int,m:Int):Int{
return m+n
}
//调用
fun main() {
val aValue = exampleFun(2,3,::aFun)
println("aFun作为参数的返回值:$aValue")
val bValue = exampleFun(2,3,::bFun)
println("bFun作为参数的返回值:$bValue")
}
//输出结果
aFun作为参数的返回:6
bFun作为参数的返回:5
这里有一点就是我们需要定义多个逻辑不同的函数,相对来说有点麻烦,但是其实我们可以看到,传的函数参数是可以简化成lamba表达式,之前我们学lamba表达式的可以知道:
val func:(Int) -> Unit = {p:Int -> println§ }
那上述的block对应的lamba表达式就是:
val block:(Int,Int) -> Int = {a:Int,b:Int -> a+b }
其中a+b就是执行逻辑,可以是a+b,a-b或者a*b,只要返回Int类型就行。
那前面的高级函数调用可以灵活写成
val aValue = exampleFun(2,3){a,b -> a+b}
二、内联函数
内联函数就是在函数前面加上 inline
关键字修饰。内联函数的使用可以减少函数的调用,有性能开销的优化作用,这里高阶函数和和内联会更匹配,原因在于调用高阶函数的时候,作为函数参数的变量调用也会是一个函数的调用,这个时候就会创建一个匿名类对象代替Lamba表达式,对象创建越多,内存开销肯定会大。看看下面这段代码
fun inlineFun(block:() -> Unit){
val curTime = System.currentTimeMillis()
block()
println(System.currentTimeMillis() - curTime)
}
//inlineFun的lamba表达式方式写法
inlineFun{
println("Kotlin")
}
//这个时候,我们只是想调用打印这个方法,看看它调用的时间,但这个时候依旧会创建lamba表达式的匿名类,
//这样消耗的时间可能会大于打印时间
//那我们用定义为内联函数后的调用是如何的
inline fun inlineFun(block:() -> Unit){
val curTime = System.currentTimeMillis()
block()
println(System.currentTimeMillis() - curTime)
}
//这个时候再调用
inlineFun{
println("Kotlin")
}
//其实际执行的是
val curTime = System.currentTimeMillis()
println("Kotlin")
println(System.currentTimeMillis() - curTime)
这样编译时创建lamba表达式匿名类开销就没了,所以内联函数的原理是:Kotlin在编译时会把内联函数内代码搬到要调用的地方, 就好像我们写了这段代码一样。
内联函数的noinline和crossinline
一个高阶函数如果被inline
关键字修饰,那么它接收的所有函数类型的参数均会被内联,如果想某个函数类型的参数不被内联,就需要用关键字noinline
修饰。
那这就有一个问题:既然前面我们说内联函数能消除Lambda表达式运行时带来的额外内存开销,那么为啥还提供了一个noinline
来排除内联呢?
这里主要是因为内联函数类型的参数只能传递给内联函数,而非内联则可以传递给任何函数,另外不同的一点是内联函数可以返回外部函数,就是调用该内联函数的函数,而非内联函数则返回局部。
下面代码看看内联函数的返回和非内联函数的返回:
inline fun nonLocalReturn(block:() -> Unit){
block()
}
fun main(){
println("main start")
nonLocalReturn{
println("lambda start")
//这里return 就是直接返回到main函数了
return
println("lambda end")
}
println("main end")
}
//执行打印的结果是
main start
lambda start
说明直接返回到main函数了,没有再往下执行
//如果我们没有定义成内联函数
fun nonLocalReturn(block:() -> Unit){
block()
}
fun main(){
println("main start")
nonLocalReturn{
println("lambda start")
return@nonLocalReturn
println("lambda end")
}
println("main end")
}
//这里打印的结果是
main start
lambda start
main end
说明仅仅是返回了nonLocalReturn函数,main函数还是往下走了
crossinline关键字
绝大多数高阶函数都可以声明为内联函数,但是也有例外的情况。如下列代码:
inline fun Runnable(block:() -> Unit):Runnable{
return object:Runnbale {
override fun run(){
block()
}
}
}
这个时候idea会提示block()调用错误,这是因为有可能存在不合法的non-local return,block()的调用处与定义处不在一个调用上下文。
那么如何解决这个问呢?
使用crossinline关键字修饰传递的函数就可以使它在Lambda表达式中一定不return,而且crossinline
修饰除了不return之外,其他内联函数的属性都是一样的。
三、几个常用的高阶函数
let函数
let函数调用一般是针对一个定义在特定情况下使用的变量,例如我们可以避免对变量的null判断。观察如下代码:
// any 不为null 时才会调用let 函数
any?.let {
//it 就是代表any对象
// todo()方法就是any对象的方法
// it.todo()的返回值作为let函数的返回值返回
it.todo()
}
在Android开发中的实际场景就是我们可能要对一个变量进行多次判空才调用,如:
mTextView?.text = "Kotlin"
mTextView?.textSize = 16f
使用let函数后可以简写为
mTextView?.let{
it.text = "Kotlin"
it.textSize = 16f
}
run函数
run函数其实就是let函数的升级版,run函数接受一个lambda 函数为参数,传入this并以闭包形式返回,返回值是最后的计算结果,那么上述的代码可以优化成下面这样:
mTextView?.run{
text = "Kotlin"
textSize = 16f
}
apply函数
apply函数和run函数的结构模式很相像,但是apply函数返回的是调用对象本身,因为apply函数的这个特性,所以它特别有助于我们进行多级的判null行为。下面我们通过一个代码示例了解一下:
apply函数的一般结构:
val applyRes = any.apply{
//todo()是 any 对象的共有属性或方法
todo()
// 最后返回的是any对象,而不是2
1+1
}
apply的简单应用举例:
//定义了一个公司对象 包含部门和人员名字
class Company(var department:Department? = null){
class Department(var departmentName: String? = null,var person:Person? = null){
class Person(var name:String? = null)
}
}
fun main() {
val company:Company? = Company(Company.Department("一部", Company.Department.Person("Jeremy")))
company?.department?.apply {
departmentName = "二部"
}?.person?.name.also {
println("这个${company?.department?.departmentName}的员工是$it")
}
}
打印的信息是:这个二部的员工是Jeremy
also函数
上述的apply应用举例中,我们最后调用了also函数,这个函数的作用和let函数是类似的,其中it就是返回的调用对象本身,其一般的结构格式就是:
val alsoRes = any.also {
//it代表的就是any todo是any的属性方法
it.todo()
1+1 //返回的是any对象,而不是2
}
use函数
use函数最大的一个特征就是会自动关闭调用者,无论其中间是否出现异常,因为use函数内部实现也是通过try-catch-finally块捕捉的方式,而close操作在finally里面执行,所以无论是正常结束还是出现异常,都能正确关闭调用者。
怎么才能调用use函数呢?
凡是实现了Closeable接口的对象都可以调用use函数。
因为use函数使得Kotlin中对File对象和IO流操作变得行云流水。
File("build.gradle").inputStream().reader().buffered()
.use {
print(it.readLines())
四、集合变换序列
4.1、filter操作
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
``
四、集合变换序列
4.1、filter操作
[外链图片转存中…(img-l14CP0HU-1715895534740)]
[外链图片转存中…(img-xau70MaA-1715895534741)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!