【Kotlin】Kotlin的高阶函数与Lambda表达式

前言

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

Kotlin的高阶函数与Lambda表达式是Kotlin的两大特色,使用频率非常高。熟悉它的用法与本质对于简化代码、提升开发效率非常有帮助。
这两个概念不是同一个东西,但是又有非常紧密的关联。这篇文章带你弄懂什么是高阶函数,以及Lambda表达式的本质和使用。

一、什么是高阶函数

高阶函数就是将函数类型用作参数或返回值的函数,例如:

函数类型当作参数
  fun a(arg:(Int)->String):String{
    arg(1)
  }
函数类型当作返回值
  fun b(arg:(Int)->String):(Int)->Unit{
    ...
  }
什么是函数类型

Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明。
例如onClick的赋值操作:

val onClick: (View) -> Unit = ……

其中(View) -> Unit就是一种函数类型。

二、在Java里如何将函数当作参数传给另一个函数

在Java里不能将函数当作参数传给另一个函数,但是有一个变通的方案,就是将函数包装成Interface。比如点击事件,将onClick函数包装成OnClickListener传递给View的setOnClickListener函数:

public interface OnClickListener{
    void onClick(View view) 
}

OnClickListener onClickListener=new OnClickListener{
  	@Override
  	public void onClick(View view){
  	  doSomething();
    }
}
view.setOnClickListener(onClickListener);

View的源码简化:

public class View{

	OnClickListener mOnClickListener;
	
	public void setOnClickListener(OnClickListener onClickListener){
	
		this.mOnClickListener=onClickListener
	
	}
	
	public void onTouchEvent(MotionEvent motionEvent){
	    ...
		mOnClickListener.onClick(this);
		...
	}
	
}

三、在Kotlin里如何将函数当作参数传给另一个函数

先看这两个函数:

   //定义一个参数为函数类型的函数(高阶函数)
  fun a(arg:(Int)->String):String{
    arg(1)
  }

   //定义一个接受Int返回String的函数
  fun b(arg:Int):String{
    return arg.toString()
  }

如何在调用a的时候将b作为实参传给a呢?首先要将函数b实例化变成对象才能传递。

(1)使用双冒号实例化函数
 
 a(b)  //错误写法,编译不通过。不能将函数本身直接传递。b没有实例化,不是对象
 
 a(::b) //正确写法,将b用双冒号实例化再进行传递
 
 val d= ::b  //将b实例化赋值给d
 
 a(d) //正确写法,d已经是函数对象。

(2)使用匿名函数实例化函数

所谓匿名函数,就是没有名字的函数,比如

fun(arg:Int):String{  //跟正常函数比,没有函数名字
    return arg.toString()
}

匿名函数不是一个函数类型,它其实是函数实例(对象)。

继续看如何调用上面的高阶函数a:

 //正确写法。调用a函数,传递一个匿名函数进去。
 a(fun(arg:Int):String{
    return arg.toString()
  })
  
  //将匿名函数赋值给d
 val d=fun(arg:Int):String{
    return arg.toString()
  }
  
 a(d) //正确写法。d已经是匿名函数。

在kotlin里如何定义OnClickListener?

fun setOnClickListener(onClick:(View)->Unit){
	this.onClick=onClick
}
view.setOnClickListener(fun(v:View)->Unit){
  	doSomething()
})
(3)、使用Lambda表达式实例化函数
view.setOnClickListener{
	 doSomething()
}

四、Lambda表达式

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

Lambda表达式是匿名函数的简化形式。所以Lamdba表达式跟匿名函数一样,它不是一个函数类型,它是函数实例(对象)。

完整的lambda表达式示例:

 { x: Int, y: Int -> x + y }

其中“->”前面是参数列表,“->”后面是函数实现。

将上文示例中的setOnClickListener传递的匿名函数进行简化,写成Lambda表达式:

//不用lambda表达式,使用传递匿名函数:
view.setOnClickListener(fun(v:View)->Unit){
  doSomething()
})

//使用Lambda表达式的写法:
view.setOnClickListener({v:View->
  doSomething()
})
Lambda表达式的特点

(1)、如果Lambda是函数的最后一个参数,可以将Lambda写在括号外面:

view.setOnClickListener(){v:View->
  doSomething()
}

(2)、如果Lambda是函数唯一的参数,还可以将括号去掉:

view.setOnClickListener{v:View->
  doSomething()
}

(3)、如果Lambda内部只有一个参数,可以省略掉不写:

view.setOnClickListener{
  doSomething()
}

需要使用这个省略的参数时,用it代表即可。

view.setOnClickListener{
  doSomething()
  it.setVibility(View.GONE)
}

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

(4)、Lambda表达式的返回值不能用return,只能使用最后一行代码表示返回值:

  //编译报错
 val d :(Int)->String  = {
    return it.toString()  //编译报错:return is not allowed here
  }
  
 //正确
 val d :(Int)->String  = {
     it.toString() 
  }
Lambda参数类型哪些情况下不能省略?

在上面的示例中,可以省略参数类型是因为setOnClickListener函数已经声明了参数类型,Lambda可以从上下文推断出来。而当我们将Lambda表达式赋值给一个变量时,不能省略参数类型,示例:

  //正确,将匿名函数赋值给d
 val d = fun(arg:Int):String{
    return arg.toString()
  }
  
  //错误,不能省略参数类型
 val d = {
      it.toString() //报错
  }
 

在赋值操作时如果一定要省略Lambda表达式类型,必须给左边变量指明类型,示例:

  //正确 ,在左边的变量指明类型
 val d :(Int)->String  = {
     it.toString()
  }

五、Lambda表达式的原理

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

Lambda表达式一般是作为函数传递给另外一个函数当作实参,我们先看看在Kotlin里函数这个“一等公民”是怎么实现的,它为什么可以脱离类而独立存在?然后写一个setCallback接口,接受一个函数作为实参,调用时使用Lambda表达式传递。我们看看它们的字节码是如何实现的,看看kotlin中的函数和Lambda表达式到底是个什么东西。

新建一个文件KotlinTest.kt,内容如下:

package com.devnn.bean

//测试函数是如何独立存在的
fun myTest(){
    println("myTest invoked")
}

//测试函数作为形参
fun setMyCallback(callback:(Int)->Unit){
    callback.invoke(100)
}

然后在MainActivityonCreate方法中调用这两个函数:

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        myTest()
        setMyCallback { println({"setMyCallback,value=${it}"}) }
   }

rebuild一下工程,然后查看KotlinTest的字节码:

// class version 52.0 (52)
// access flags 0x31
public final class com/devnn/bean/KotlinTestKt {

  // compiled from: KotlinTest.kt

  @Lkotlin/Metadata;(mv={1, 6, 0}, k=2, xi=48, d1={"\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0002\u0008\u0002\n\u0002\u0018\u0002\n\u0002\u0010\u0008\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a\u001a\u0010\u0002\u001a\u00020\u00012\u0012\u0010\u0003\u001a\u000e\u0012\u0004\u0012\u00020\u0005\u0012\u0004\u0012\u00020\u00010\u0004\u00a8\u0006\u0006"}, d2={"myTest", "", "setMyCallback", "callback", "Lkotlin/Function1;", "", "app_debug"})

  // access flags 0x19
  public final static myTest()V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
   L0
    LINENUMBER 5 L0
    LDC "myTest invoked"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L1
    LINENUMBER 6 L1
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

  // access flags 0x19
  // signature (Lkotlin/jvm/functions/Function1<-Ljava/lang/Integer;Lkotlin/Unit;>;)V
  // declaration: void setMyCallback(kotlin.jvm.functions.Function1<? super java.lang.Integer, kotlin.Unit>)
  public final static setMyCallback(Lkotlin/jvm/functions/Function1;)V
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "callback"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 9 L1
    ALOAD 0
    BIPUSH 100
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    INVOKEINTERFACE kotlin/jvm/functions/Function1.invoke (Ljava/lang/Object;)Ljava/lang/Object; (itf)
    POP
   L2
    LINENUMBER 10 L2
    RETURN
   L3
    LOCALVARIABLE callback Lkotlin/jvm/functions/Function1; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

从中可以发现以下几个结论:

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

(1)、kotlin的文件其实被编译成了一个类,文件.kt后缀去掉了点当作了类名结尾。
(2)、kotlin中的函数独立声明,其实是一个静态函数,这个静态函数所属的类就是文件名对应的类。
(3)、函数作为另一个函数的形参类型时,其实被替换成了接口FunctionX(x代表有几个参数)。本例中因为callback只有一个参数,因为编译成了Function1。而Function1其实是一个普通的接口:

//A function that takes 1 argument.
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}

再看看MainActivityonCreate函数的字节码:

...省略无关代码
  final static INNERCLASS com/devnn/demo/MainActivity$onCreate$1 null null
...省略无关代码
 protected onCreate(Landroid/os/Bundle;)V
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
   L0
    LINENUMBER 61 L0
    ALOAD 0
    ALOAD 1
    INVOKESPECIAL androidx/appcompat/app/AppCompatActivity.onCreate (Landroid/os/Bundle;)V
   L1
    LINENUMBER 62 L1
    INVOKESTATIC com/devnn/bean/KotlinTestKt.myTest ()V
   L2
    LINENUMBER 63 L2
    GETSTATIC com/devnn/demo/MainActivity$onCreate$1.INSTANCE : Lcom/devnn/demo/MainActivity$onCreate$1;
    CHECKCAST kotlin/jvm/functions/Function1
    INVOKESTATIC com/devnn/bean/KotlinTestKt.setMyCallback (Lkotlin/jvm/functions/Function1;)V
   L3
    LINENUMBER 66 L3
    LDC "MainActivity"
    LDC "onCreate"
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP

(1)、调用一个kotlin文件级别的函数时,其实就是调用这个文件对应类的静态函数。
(2)、函数类型的Lambda表达式其实是一个匿名内部类的实例,将其强转成FunctionX类型,再传递给另一个函数当作实参。

六、总结

(1)、在kotlin里,函数与类都是一等公民,函数也可以实例化作为参数传递给另一个函数,函数也可以当作函数的返回值。
(2)、函数需要实例化变成对象才可以进行传递,使用双冒号、匿名函数、Lambda表达式都可以将函数实例化。
(3)、函数独立声明时其实是一个静态函数
(4)、函数作为另一个函数的形参时,它的类型其实是一个FunctionX接口。
(5)、Lambda表达式传给另一个函数作为实参时,这个Lambda表达式其实是一个匿名内部类。

如有不足,欢迎指正~

本文来自https://blog.csdn.net/devnn,未经允许禁止转载

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值