当前位置: 首页 > 科技观察

Android进阶Kotlin高阶函数和Lambda表达式详解

时间:2023-03-14 11:25:12 科技观察

前言Lambda语法在Java中已经被广泛使用,几乎我们在Android开发的每一个项目都会在项目中接入Lambda插件,因为Lambda确实可以减少很多代码。巧合的是,Kotlin中也使用了Lambda语法。在这篇文章中,我将详细讲解Lambda语法的编写和使用。一、kotin高阶函数详解1、高阶函数就是以函数为参数或返回值的函数。这种函数的一个很好的例子是lock(),它接受一个锁对象和一个函数,获取锁,运行函数并释放锁:funlock(lock:Lock,body:()->T):T{lock.lock()try{returnbody()}finally{lock.unlock()}}body具有函数类型:()->T,因此它应该是一个不带参数并返回值的函数类型T。它在try{:.keyword}代码块内调用,受锁保护,其结果由lock()函数返回。如果我们要调用lock()函数,我们可以将另一个函数作为参数传递给它(参见函数参考)2.如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么function称为高阶函数函数类型,基本规则如下:(String,Int)->Unit现在在函数的参数声明或返回值声明中加上上述函数类型,那么这个函数就是一个高阶函数,比如funexample(func:(String,Int)->Unit){func("hello",123)}可以看出这里的example()函数接收的是一个函数类型的参数,所以example()函数是一个高阶函数;在这里,我将定义一个名为num1AndNum2()的高阶函数,它接受两个整数和一个函数类型参数。我们将对num1AndNum2()函数中传入的两个整型参数进行运算,并返回运算结果。但具体操作由传入的函数类型参数决定。新建一个文件,命名为Test1.ktfunnum1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{valresult=operation(num1,num2)returnresult}funplus(num1:Int,num2:Int):Int{returnnum1+num2}funminus(num1:Int,num2:Int):Int{returnnum1-num2}main()函数funmain(){valnum1=100valnum2=80valresult1=num1AndNum2(num1,num2,::plus)valresult2=num1AndNum2(num1,num2,::minus)println("result1:"+result1)println("result2:"+result2)}result1:180result1:20::plus和::minus的写法是函数引用的一种方式,意思是plus()和minus()函数作为参数传递给num1AndNum2()函数。先定义一个匹配其函数类型参数的函数会不会太复杂了?是的,所以Kotlin还支持许多其他调用高阶函数的方式,例如Lambda表达式、匿名函数、成员引用等。其中,Lambda表达式是调用高级函数最常见、最普遍的方式。刚才的代码使用了Lambda表达式来实现(最后一行Lambda表达式自动作为返回值),plus()和minus()函数可以去掉。funmain(){valnum1=100valnum2=80valresult1=num1AndNum2(num1,num2){n1,n2->n1+n2}valresult2=num1AndNum2(num1,num2){n1,n2->n1-n2}println("result1:"+result1)println("result2:"+result2)}funnum1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{valresult=operation(num1,num2)returnresult}3.关闭封装函数闭包函数一个函数的返回值是一个函数,函数内部包含另外一个函数,可以是带参数但不带参数的匿名函数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():(字符串)->(字符串){varcurrent=0;returnfun(str:字符串):字符串{current++;return"$str---$current";}}4.kotinfunmain(args:Array){vallist=listOf(1,2,3,4,5,6)valnewList=list.map{//对集合中的数据进行操作,然后赋值给新的集合(it*2).toString()}.forEach(::println)//24681012valdoubleList=list.map{it.toDouble()}.forEach(::print)//1.02.03.04.05.06.0//作为参数调用函数的第二种方式类名::方法名valdoubleList2=list.map(Int::toDouble).forEach(::print)////1.02.03.04.05.06.0}flatMap转换collectionsfunmain(args:Array){vallist=arrayOf(1..5,50..55)//把多个数组集合变成一个数组,对数据进行转化valmergeList=list.flatMap{intRange->//集合中的集合1..5,50..55intRange.map{intElement->//集合中的遍历集合1,2,3,4,5"No.$intElement"}}//No.1,No.2,No.3,No.4,No.5,No.50,No.51,No.52,No.53,No.54,No.55,mergeList.forEach{print("$it,")}println()//直接tran将多个数组集合sform成一个组合集合valnewList=list.flatMap{it}//1,2,3,4,5,50,51,52,53,54,55,newList.forEach{print("$it,")}}filterfilterfunmain(args:Array){vallist=arrayOf(1..5,2..3)valnewList=list.flatMap{it}//过滤集合itemvalfilterList=newList.filter{it>2}filterList.forEach(::print)//3453//filter集合中的下标为奇数itemvalfilterIndexList=newList.filterIndexed{index,i->index%2==0;}filterIndexList.forEach{print(it)}//1353}forEachfunmain(args:Array){varlist=listOf(1,2,3,4,5,6)list.forEach(::println)valnewList=arrayListOf()--->1,2,3,4,5,6list.forEach{newList.add((it*2).toString())--->2,4,6,8,10,12}newList.forEach(::println)}下面介绍一下Lambda二、Lambda表达式详解1、什么是Lambda表达式?Lambda表达式是JDK8引入的一个重要的新特性。实际上,Lambda表达式的本质只是一个“语法糖”。如果你习惯了面向对象编程的思想,一开始可能不习惯这种语法形式,但是如果你学过C#,你会发现它的语法很像C#中的“delegate”;大家都知道Java中万物皆对象,Java也一直致力于保持其面向对象的特性。虽然函数对于Java来说很重要,但是在Java的世界里,函数不能独立存在,只能依赖对象来调用在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将它们赋值给一个变量,或者将它们作为参数传递给其他函数。JavaScript是函数式编程语言最典型的代表;函数式语言提供了一个强大的功能——闭包,它比传统的编程方法有很多优点。闭包是一个可调用对象,它记录了创建它的作用域中的一些信息。因此,Java提供的与闭包最接近的概念是Lambda表达式。尽管闭包和Lambda表达式之间存在显着差异,但至少Lambda表达式是闭包的良好替代品;使用Lambda表达式的目的是为了替代大部分的匿名内部类,让我们可以写出更加简洁优雅的Java代码,尤其是在集合遍历等集合操作方面,可以大大优化代码结构。JDK还提供了大量内置的函数式接口供我们使用,使得Lambda表达式的使用更加方便高效;如果不精通Lambda表达式,不建议乱用,因为你也可以使用Lambda表达式来实现相应的功能,只是作为锦上添花的工具;2.Lambda表达式语法结构Lambda表达式基本语法结构如下:(parameters)->expressionor(parameters)->{statements;}其中()用于描述参数列表,{}用于描述方法body,->是lambda运算符,读作(goesto),parameters代表参数,expression代表表达式,statements代表代码块。结构描述如下:一个Lambda表达式可以有零个或多个参数参数的类型可以显式声明或从上下文中推断。例如:(inta)与(a)的效果相同所有参数必须包含在括号中,以逗号分隔。例如:(a,b)or(inta,intb)or(Stringa,intb,floatc)空括号表示参数集为空。例如:()->42当只有一个参数且可以推导出其类型时,括号()可以省略。例如:a->returna*aLambda表达式的主体可以包含零个或多个语句。如果Lambda表达式的主体只有一条语句,则大括号{}可以省略。匿名函数的返回类型与函数体表达式一致;如果Lambda表达式的主体包含多个语句,则表达式必须用花括号{}括起来(形成一个代码块)。匿名函数的返回类型与代码块的返回类型一致。无归则空;3.Lambda表达式的重要特点可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。可选参数括号:一个参数不需要定义括号,多个参数需要定义括号。可选花括号:如果主体包含语句,则不需要花括号。可选return关键字:如果body只有一个表达式返回值,编译器会自动返回值,花括号需要指定表达式返回值。4.函数式接口详解①什么是函数式接口java中的函数式接口是指只有一个抽象方法的接口。函数式接口是适合函数式编程场景的接口。在Java中,函数式编程体现在Lambda中,所以函数式接口是可以应用于lambda的接口。只有保证接口中只有一个抽象方法,才能顺利推导lambda;②@FunctionalInterface注解的语法与@override注解类似。这个住所适用于功能接口的定义。一旦使用该注解定义了函数式接口,编译器会检查该接口是否只有一个抽象方法@FunctionalInterfacepublicinterfaceMyFunctionalInterface{voidmyMethod();}将函数式接口作为方法的参数publicclassDemo{privatestaticvoiddos(FunctionInterfacefi){fi.method();}publicstaticvoidmain(String[]args){Demo.dos(()->{System.out.println("lambda表达式");});}5.Lambda表达式基本用例表达式基本公式的用例//1。不需要参数,返回值为5()->5//2。接收一个参数(number类型),返回其double值x->2*x//3。接受2个参数(数字),并返回它们的差值(x,y)->x–y//4。接收2个int整数,返回它们的和(intx,inty)->x+y//5.接受一个字符串对象,在控制台打印,不返回任何值(看起来像返回void)(Strings)->System.out.print(s)定义了6个接口,后面我们会根据这6个接口来演示案例/**多参数无返回*/@FunctionalInterfacepublicinterfaceNoReturnMultiParam{voidmethod(inta,intb);}/**无参数无返回值*/@FunctionalInterfacepublicinterfaceNoReturnNoParam{voidmethod();}/**一个参数无返回值*/@FunctionalInterfacepublicinterfaceNoReturnOneParam{voidmethod(inta);}/**多个参数有返回值*/@FunctionalInterfacepublicinterfaceReturnMultiParam{intmethod(inta,intb);}/**没有参数有返回值*/@FunctionalInterfacepublicinterfaceReturnNoParam{intmethod();}/**一个参数有返回值*/@FunctionalInterfacepublicinterfaceReturnOneParam{intmethod(inta);}案例代码:publicclassTest2{publicstaticvoidmain(String[]args){//1.简化参数类型,参数类型可以不写,但所有参数一定不能写NoReturnMultiParamlamdba1=(a,b)->{System.out.println("简化参数类型");};lamdba1.method(1,2);//2。简化参数括号,如果只有一个参数,参数括号可以省略NoReturnOneParamlambda2=a->{System.out.println("简化参数括号");};lambda2.method(1);//3.简化方法体大括号,如果方法栏只有一条语句,可以省略方法体花括号,类似iforforNoReturnNoParamlambda3=()->System.out.println("简化方法体花括号");lambda3.method();//4.如果方法体中只有一条语句,并且是return语句,可以省略方法体的大括号ReturnOneParamlambda4=a->a+3;System.out.println(lambda4.method(5));ReturnMultiParamlambda5=(a,b)->a+b;System.out.println(lambda5.method(1,1));}}6.使用lambda表达式引用方法①引用方法的语法为:methodowner::methodname注:静态方法属性为类名,普通方法为对象此代码示例结合了顶级自定义功能接口:publicclassExe1{publicstaticvoidmain(String[]args){ReturnOneParamlambda1=a->doubleNum(a);System.out.println(lambda1.method(3));//lambda2引用了已经implementeddoubleNummethodReturnOneParamlambda2=Exe1::doubleNum;System.out.println(lambda2.method(3));Exe1exe=newExe1();//lambda4引用了实现的addTwo方法ReturnOneParamlambda4=exe::addTwo;System.out.println(lambda4.method(2));}/***要求*1。参数的数量和类型必须与接口中定义的一致*2。返回值类型要和接口中定义的保持一致*/publicstaticintdoubleNum(inta){returna*2;}publicintaddTwo(inta){returna+2;}}②构造方法的引用一般我们需要在接口中声明,用作对象的生成器,通过类名::newObject实例化,然后调用方法返回对象。interfaceItemCreatorBlankConstruct{ItemgetItem();}interfaceItemCreatorParamContruct{ItemgetItem(intid,Stringname,doubleprice);}publicclassExe2{publicstaticvoidmain(String[]args){ItemCreatorBlankConstructcreator=()->newItem();Itemitem=creator.creatorItemk=CreatorItem2(关键用法。对于匿名类,关键字this被解释为匿名类,而对于Lambda表达式,关键字this被解释为Lambda7中编写的外部类。Lambda再次总结了无参数无返回值的函数类型(Unit的返回类型不能省略):()->Unit;接受T类型参数且无返回值的函数类型:(T)->Unit;接受T类型参数和A类型参数且有无返回值(多参数同理):(T,A)->Unit;接受类型T的参数,返回类型R的值的函数类型:(T)->R;接受参数的函数类型类型T和类型A并返回一个值R类型(多个参数相同):(T,A)->R;在花括号{}的方法实现中,如果只有一个参数,可以用它来指定,不用写x->...的代码;如果有多个参数,则需要x:Int,y:Int->...该方法在->后面指定自定义参数名;小结1.Lambda和高阶函数理解起来有点混乱,需要大量的练习和实验才能慢慢理解2.Lambda很深,大家一起学习进步