前言在Kotlin中,高阶函数是指将一个函数作为另一个函数的参数或返回值。如果用f(x)和g(x)来表示两个函数,那么高阶函数可以表示为f(g(x))。Kotlin为开发者提供了丰富的高阶函数,比如Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach。为了能够自如地使用这些高阶函数,我们有必要了解如何使用这些高阶函数。今天我们来讲解一下高阶函数1.高阶函数详解1.什么是高阶函数?如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么这个函数就称为高阶函数。与java不同的是,Kotlin中加入了一个函数类型的概念。如果我们把这个函数加入到一个函数的参数声明或者返回值声明中,那么这就是一个高阶函数。函数类型语法的基本规则:(String,Int)->Unit添加到函数的参数声明中publicfuntest2(test:Int,block:()->Unit){varv=block()DTLog.i("TestTest","Test1")}publicfunT.test22(block:()->T):T{returnblock()}publicfunT.test26(block:T.()->Unit){block()}publicfunT.test23(block:(T)->Unit):T{returnthis}publicfunvart=block(this)return}publicfunreturnblock(this)}上面是一个高阶函数,接收一个函数类型的参数,调用高阶函数的方法-order函数与调用普通函数没有太大区别,只需要在参数名后面加上括号,在括号内传入必要的参数即可;高阶函数类型有一个特殊的符号对应于函数签名,即它们的参数和返回值:所有函数类型都有一个用括号括起来的参数类型列表和一个返回类型:(A,B)->C表示接受两个类型A和B的参数并返回类型C的值的函数类型。参数类型列表可以为空,如()->A,返回值可以为空,如(A,B)->Unit;函数类型可以有一个额外的接收者类型,它在符号之前指定,例如,类型A.(B)->C表示函数以类型B作为参数并返回类型的值C可以在A的接收者对象上调用。还有一种特殊类型的函数,suspend函数,它的表示法中有一个suspend修饰符,例如suspend()->Unit或suspendA.(B)->C。2、内联函数详解①什么是内联函数inline(注意,不是在线),翻译成“内联”或“嵌入”。意思是:当编译器发现某段代码在调用内联函数时,不调用该函数,而是将该函数的全部代码插入到当前位置。这样做的好处是省去了调用的过程,加快了程序的运行速度。(函数的调用过程总是因为前面提到的参数入栈等操作而花费更多的时间)。这样做的缺点是每当代码调用一个内联函数时,需要在调用处直接插入一段函数代码,这样会增加程序的体积。用生活现象来比喻,就好像电视机坏了,你觉得太慢了,找修理工也来不及了,干脆在家里留了个修理工。这样当然更快,但是修理工住你家会占很多地方。内联函数不是必需的,它只是速度的装饰。将函数修饰为内联,使用如下格式:内联函数的声明或定义是一个简单的语句,在函数声明或定义前加上一个内联修饰符。inlineintmax(inta,intb){return(a>b)?a:b;}内联函数的本质是节省时间但消耗空间。②内联函数的规则内联函数的规则(1)、函数可以调用自身,称为递归调用(后述),包含递归调用的函数不能设置为内联;(2)、使用复杂的流程控制语句:循环语句和switch语句不能设置为inline;(3)、由于inline增加体积的特点,建议inline函数中的代码尽量短。最好不要超过5行。(4)、inline仅作为“请求”使用。在某些情况下,编译器会忽略inline关键字并强制函数成为普通函数。发生这种情况时,编译器会给出警告信息。(5)在你调用一个内联函数之前,这个函数必须先被声明或定义为内联函数。如果在前面声明为普通函数,在调用代码后定义为内联函数,程序可以通过编译,但函数不是内联实现的。例如下面的代码片段://函数一开始没有声明为inline:voidfoo();//然后有代码调用它:foo();//函数调用后定义为inline:inlinevoidfoo(){......}代码中的foo()函数没有内联实现;(6)、为了调试方便,当程序处于调试阶段时,所有的内联函数都没有实现③内联函数要注意以下几个问题(1)在一个文件中定义的内联函数不能在另一个文件中使用.它们通常在头文件中共享。(2)内联函数要简洁,只有几条语句。如果语句很多,不适合定义为内联函数。(3)在内联函数体中,不能有循环语句、if语句或switch语句,否则,即使函数定义中有inline关键字,编译器也会将该函数视为非内联函数。(4)内联函数必须在调用函数之前声明。关键字inline必须和函数定义体放在一起才能使函数内联,只是把inline放在函数声明的前面是没有作用的。3、将高阶函数中inline函数直接使用的lambda表达式,在底层转化为匿名类的实现方法。这意味着我们每次调用Lambda表达式时,都会创建一个新的匿名类实例,这当然会造成额外的内存和性能开销。为了解决这个问题,Kotlin提供了内联函数的功能,可以彻底消除使用Lambda表达式带来的运行时开销。定义高阶函数时只需要加上inline关键字的声明即可。inlinefuntest111(num1:Int,num2:Int,block:(Int,Int)->Int):Int{valresult=block(num1,num2)returnresult}4.闭包函数函数的返回值是一个函数,函数内部包含另外一个函数,可以是带参数和不带参数的匿名函数funmain(args:Array){valmm=aaa()println(mm())println(mm())println(mm())println(mm())println(mm())valkk=bbb()println(kk("shadow"))//shadow---1println(kk("shadow"))//shadow---2println(kk("shadow"))//shadow---3println(kk("shadow"))//shadow---4println(kk("shadow"))//shadow---5}//闭包函数是作为返回参数funaaa():()->(Int){varcurrent=10returnfun():Int{returncurrent++}}funbbb():(String)->(String){varcurrent=0;returnfun(str:String):String{current++;return"$str---$current";}}二、Kotlin中的标准库Standard.kt源码讲解提供了一些方便的内置高阶函数(let,also,with,run)在t的Standard.kt标准库中heKotlinsourcecode,apply),可以帮助我们写出更简洁优雅的Kotlin代码,提高开发效率,学习源码可以帮助我们更快的理解和应用1、apply@kotlin.internal.InlineOnlypublicinlinefunT.apply(block:T.()->Unit):T{contract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}block()returnthis}将this作为block函数参数传递(调用时可省略),returnapply函数的值是调用者本身;执行一个T类型的方法、变量等,然后返回自己T;注意参数block:T.(),每当看到block:T.()->这种代码块的意思是在Braces{}中可以直接调用T里面的API,不用加T。这个【其实叫这个,这。一般省略]valstr="hello"str.apply{length}//可以省略str.str.apply{this.length}//可以这样2.let@kotlin.internal.InlineOnlypublicinlinefuncontract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}returnblock(this)}let方法是传递类型T,返回另一个类型R的形式;作为block函数参数传递,let函数的返回值由block函数决定;3.also@kotlin.internal.InlineOnly@SinceKotlin("1.1")publicinlinefunT.also(block:(T)->Unit):T{contract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}block(this)returnthis}执行T类型中的方法、变量等,然后返回自身T;作为块函数参数传递(调用时不能省略),并且also函数的返回值是调用者本身;该方法与上面的apply方法类似,只是在花括号中执行T自己的方法时,必须加上T。否则无法调用T中的API。这是什么意思?请参见以下代码:valstr="hello"str.also{str.length}//st河必须加,否则编译报错str.also{it.length}//或使用它。4、with@kotlin.internal.InlineOnlypublicinlinefuncontract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}returnreceiver.block()}with()方法接收一个T类型的参数和一个代码块,处理后返回一个R类型的结果valstr="hello"valch=with(str){get(0)}println(ch)//printh5,run@kotlin.internal.InlineOnlypublicinlinefunrun(block:()->R):R{contract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}returnblock()}要求传递一个代码块,返回任意类型;任何函数接收使用代码块时,一般建议使用{}来包含代码块中的逻辑。只有在某些特殊情况下才能简化为参数形式(::fun)@kotlin.internal.InlineOnlypublicinlinefuncontract{callsInPlace(block,InvocationKind.EXACTLY_ONCE)}returnblock()}这里是执行一个T型的run方法,并且传递的仍然是代码块,但是内部执行的是T的内部变量或者方法,返回的是R类型的run{println(888)}valres=run{2+3}funrunDemo(){println("测试运行方法")}//我们可以这样运行(::runDemo)6、takeIfpublicinlinefunT.takeIf(predicate:(T)->Boolean):T?{contract{callsInPlace(predicate,InvocationKind.EXACTLY_ONCE)}returnif(predicate(这))thiselsenull}根据传入的参数T进行内部判断,根据判断结果返回null或者T本身;传递的是[unarypredicate]代码块,和C++中的unarypredicate很像:方法只包含一个参数,返回类型是Boolean类型;在源码中,是通过传入的一元谓词代码块来判断的。如果为真,则返回自身,否则返回null;valstr="helloWorld"str.takeIf{str.contains("hello")}?.run(::println)7,takeUnlesspublicinlinefunT.takeUnless(predicate:(T)->Boolean):T?{contract{callsInPlace(谓词,InvocationKind.EXACTLY_ONCE)}returnif(!predicate(this))thiselsenull}这个方法和takeIf()方法类似,只是内部判断为false时返回自己的T,为true时,返回null,就不多解释了,使用参考takeIf()方法8.repeat()publicinlinefunrepeat(times:Int,action:(Int)->Unit){contract{callsInPlace(action)}for(indexin0untiltimes){action(index)}}分析:repeat方法包含两个参数:第一个第一个参数是int类型,重复次数,第二个参数表示要重复执行的对象。该方法每次执行时都会将执行次数传递给要执行的模块。至于重复执行模块是否需要这个值,需要根据业务实际需要考虑,例如:publicinlinefunrepeat(times:Int,action:(Int)->Unit){contract{callsInPlace(action)}for(indexin0untiltimes){action(index)}}三。高阶函数选择如果需要返回给调用者本身(即returnthis),也可以选择apply如果需要将this作为参数传递,可以选择applyrunwith如果需要将其作为参数传递,也可以选择let如果返回值需要由函数决定(即returnblock()),可以选择runwithlet总结一下是Kotlin内置的高阶函数还是我们的自定义一个,传入的代码块样式无外乎如下:1.block:()->Tandblock:()->Concretetype使用(::fun)形式简化时,传入的方法必须是无参数,如果返回值类型为T,则可以是任意类型,否则返回类型必须跟这段代码block的返回类型一致2.block:T.()->Randblock:T.()->具体类型。使用(::fun)简化时,传入的方法必须包含T类型如果参数和返回值类型为R,则可以是任何类型,否则返回类型必须与本代码块的返回类型一致。比如with和apply这两个方法3、block:(T)->Randblock:(T)->concretetype当使用(::fun)简化时,传入的方法必须包含一个T类型的参数,如果返回值类型为R,则可以是任何类型,否则返回类型必须与本代码块的返回类型一致。比如let和takeIf这两个方法
