Kotlin lambda解析

Kotlin中,充斥着各种各样的Lambda 表达式,这是Kotlin最方便的特性之一

了解Kotlin 中的lambda,首先得知道Kotlin中的高阶函数


1.高阶函数

在Java中,如果有一个a方法,要去调用b方法,那么在里面直接调用即可。

int a() {
  return b(1);
}
a();

接着,如果我不想把调用b方法的参数写死,希望动态设置方法b的参数。

int a(int param) {
  return b(param);
}
a(1);
a(2);

这些在Java中很轻松就能做到,不过…
如果我们想动态设置的不是方法参数,而是方法本身呢,比如在方法a内有一处对别的方法的调用,这个方法可能是方法b,方法c,方法d…,该方法的参数类型是int,返回值类型也是int。方法a在执行时,具体需要调用哪个方法,能否动态设置? 也就是说能否将一个方法作为参数传给a?

通过接口

public interface Wrapper {
  int method(int param);
}

将这个接口类型Wrapper 作为方法a的参数

int a(Wrapper wrapper) {
  return wrapper.method(1);
}
a(wrapper1);
a(wrapper2);

举个我们常见的例子

在Android里View的点击事件

public class View {
  OnClickListener mOnClickListener;
  ...
  public void onTouchEvent(MotionEvent e) {
    ...
    mOnClickListener.onClick(this);
    ...
  }
}

OnClickListener就是一个接口,点击事件的内容全在onClick()方法里

public interface OnClickListener {
  void onClick(View v);
}

我们在给View设置点击事件时,传的就是一个OnClickListener,本质上这样传递的是一个稍后会被调用的onClick()方法,一般称之为回调,不过由于Java中不允许直接传递方法,所以需要用接口包装一下。

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

在Kotlin里,函数是可以作为一个参数被传递的 :

fun setOnClickListener(onClick: (View) -> Unit) {
  this.onClick = onClick
}
                         👇传入一个匿名方法,参数类型时View,无返回值                        
view.setOnClickListener(fun(v: View): Unit) {
  doSomething()
})

这种 参数或者返回值是函数的函数 在Kotlin中称之为高阶函数(Higher-Order-Functions)

2. Lambda表达式

在刚刚的代码中,传入的匿名函数还能进一步简化,写成Lambda表达式的形式

                         👇简化后的匿名方法                       
view.setOnClickListener( { v: View ->
  doSomething()
} )

Kotlin允许如果Lambda 是最后一个参数时,Lambda可以写在方法()的外面:

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

如果该Lambda 是唯一的参数,则方法()可以省略

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

进一步,如果该Lambda 只有一个参数,该参数还可以省略不写, Kotlin为这种唯一参数提供了默认的名字it

view.setOnClickListener { 
  doSomething()
  it.visibility = GONE
}

3. 为什么Kotlin中函数可以作为参数被传递?

将刚才的代码,反编译为Java:

                                      👇实际传递的是FunctionN类型的对象
public static final String a(@NotNull Function1 funParam) {
      Intrinsics.checkNotNullParameter(funParam, "funParam");
      return (String)funParam.invoke(1);
}
interface FunctionN<out R> : Function<R>, FunctionBase<R> {

    operator fun invoke(vararg args: Any?): R

    override val arity: Int
}

所以,之所以Kotlin中方法可以被当作参数 进行传递,是因为它实际一个FunctionN类型的实实在在的对象

对应关系:

表达式FunctionN
(String) -> UnitFunction1<String,Int>
(String,Int) -> DoubleFunction2<String,Int,Double>
(参数1类型,参数2类型…参数n类型) -> 返回值类型FunctionN<参数1类型,…,参数n类型,返回值类型>

4.inline内联函数

集合操作符 filter 可以定义规则对集合进行过滤

listOf(1,2,3,4,5,6).filter {it > 2}

看一下 filter 方法的声明

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

filter方法接收一个 函数类型的参数,并且返回值是boolean,符合使用lambda的场景,上面我们说过,lamdba是实现了FunctionN接口的对象。
如果集合里有成百上千个元素,那每次经过filter 都会生成匿名对象,是一个不容忽视的开销,引起性能问题。
所以,Kotlin中引入了内联函数的概念:

内联函数回把函数的实现直接拷贝的调用处,避免创建匿名内部类。

inline fun inlined(getString: () -> String) = println(getString()) 
 
fun notInlined(getString: () -> String) = println(getString())

fun test() {
    var testVar = "Test"

    notInlined { testVar }

    inlined { testVar }
}

编译后的java代码:

public static final void test() {
    final ObjectRef testVar = new ObjectRef();
    testVar.element = "Test Variable";
    
    // notInlined:
    //每个闭包 lamdba 都是一个对象会额外分配内存,影响运行时间
    notInlined((Function0)(new Function0(0) {
        public Object invoke() {
            return this.invoke();
        }
    
        @NotNull
        public final String invoke() {
            return (String)testVar.element;
        }
    }));

    // inlined:会直接将方法体复制到调用处,
    String var3 = (String)testVar.element;
    System.out.println(var3);
}

在 kotlin 中,因为出现了大量的带 lamdba的 高阶函数 – 「高阶函数是将函数用作参数或返回值的函数」,使得越来越多的地方出现 函数参数 不断传递的现象,每一个函数参数都会被编译成一个对象, 使得内存分配(对于函数对象和类)和虚拟调用会增加运行时间开销,通过内联化 lambda 表达式可以消除这类的开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值