Kotlin的lambda闭包

7,386 阅读5分钟

Kotlinlambda闭包跟Javalambda闭包完全是两个不同的概念。我们都知道,Javalambda表达式就是单方法接口(也就是只有一个方法的接口)匿名实现的语法糖。而Kotlinlambda闭包真正把它抽象成了一种() -> Unit类型,这种类型我们可以把它理解成一个类。作用就是写回调方法时替代单方法接口,减少一些代码量

Java的lambda表达式

上面也说了,Javalambda表达式就是单方法接口匿名实现的语法糖,(语法糖的意思就是这种语法对程序的功能没有影响,让使用起来更加简单,看起来更加简洁),下面看一下一个简单的使用

rightYAxis.setValueFormatter(new IAxisValueFormatter() {
    @Override
    public String getFormattedValue(float value, AxisBase axis) {
        return (int) value + "%";
    }
});

//Java 8 way: lambda
rightYAxis.setValueFormatter((v, a) ->
    // lambda最后一条语句的执行结果表示这个lambda的返回值
    (int) v + "%"
);

Kotlin的lambda闭包写法

lambda最后一条语句的执行结果表示这个lambda的返回值。下面看一个在Kotlin中的最常见的写法,通过lambda闭包写函数回调。

fun setOnCallBackListener(listener: (String) -> Unit) {
    // 这两种方式等效,直接调用listener闭包体
    listener("send data")
    //listener.invoke("send data")
}

fun main() {
    setOnCallBackListener({ str: String ->
        print(str)
    })
}

lambda闭包只有一个参数时,可以用it来替代。当lambda闭包是函数的最后一个参数时,可以将lambda闭包体移到()外。如果函数只有一个参数且这个参数是lambda闭包,可以省略掉(),所以上面的代码可以省略如下

fun main() {
    setOnCallBackListener{ 
        print(it)
    }
}

初学者难理解的两个问题

我刚接触Kotlinlambda表达式的时候遇到两个问题比较困惑。我估计很多人多会是这样。对于下面的setOnKotlinCallBackListener方法,参数为一个单方法接口,在Kotlin中调用的时候只支持匿名内部类的方式,不支持传入lambda表达式。但是在Java中调用的时候确是支持传lambda表达式,这是为什么?其实弄懂lambda闭包在JavaKotlin的区别这个问题比较好理解,因为在Kotlin中 下面代码中的lambda闭包跟OnKotlinCallBackListener接口是完全两种不同的类型,两种之间没有任何关系,它不是Java中的语法糖。

fun setOnKotlinCallBackListener(listener: OnJavaCallBackListener) {
    listener.onFinished("", true)
}

fun main() {
    setOnKotlinCallBackListener(object : OnJavaCallBackListener{
        override fun onFinished(data: String, success: Boolean) {
            print(data)
        }
    })
    // 报错
    setOnKotlinCallBackListener{ data, success ->
         print(data)
    }
}
public class JavaLambdaDemo {

    public static void main() {
        
        KotlinLambdaKt.setOnKotlinCallBackListener((data, success) -> {
            System.out.println(data);
        });
        
    }
}

第二个问题,如下代码,在Kotlin中调用JavasetJavaCallBackListener方法,参数为单方法接口,我们看到是支持传入lambda表达式的。这是为什么呢?其实Kotlinlambda闭包用在Java方法中变成了Javalambda语法糖,其实主要是为了能和Java完美兼容

interface OnJavaCallBackListener {
    void onFinished(String data, boolean success);
}

public class JavaLambdaDemo {

    public static void setJavaCallBackListener(OnJavaCallBackListener listener) {
        listener.onFinished("complete", true);
    }
}

fun main() {
    
    JavaLambdaDemo.setJavaCallBackListener{ str, bool -> 
         print(str)
    }
}

lambda闭包和Function的关系

Kotlin文件编译后会编译成class文件,lambda闭包编译后会被编译成一个Function接口对象,其实在Kotlinlambda闭包跟Function接口是等效的。下面的例子中,用Function1的接口对象传入参数为(String) -> Unit的方法也是支持的。

fun setOnCallBackListener(listener: (String) -> Unit) {
    listener("send data")
    //listener.invoke("send data")
}

fun main() {
    setOnCallBackListener(object : Function1<String, Unit> {
        override fun invoke(p1: String) {
            print(p1)
        }
    })

    setOnCallBackListener {
        print(it)
    }
}

其实Function1等效于只有一个参数的lambda闭包,在Kotlin中,最多只定义了Function22,也就是最多支持参数个数为22个的lambda闭包。

/** A function that takes 21 arguments. */
public interface Function21<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R
}
/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

Kotlinlambda闭包(String)->UnitFunction1<String, Unit> 是等效的,所以Java中调用Kotlinlambdab闭包,需要通过对于参数的Function接口对象来实现

    public static void main() {
         KotlinLambdaKt.setOnCallBackListener(new Function1<String, Unit>() {
             @Override
             public Unit invoke(String s) {
                 return null;
             }
         });
    }

typealias重命名

Swift一样,Kotlin也有typealias,进行类型的重命名

typealias CallBackListener = (String) -> Unit

fun setOnCallBackListener(listener: CallBackListener) {
    listener("send data")
    //listener.invoke("send data")
}

fun main() {
    setOnCallBackListener {
        print(it)
    }
}

高阶函数

高阶函数是指函数或者lambda的参数又是一个函数或者lambda闭包的函数,上面使用的例子中参数为lambda闭包的函数就是高阶函数

fun main(args: Array<String>) {
    onlyif(true, {
         println("日志信息$it")
    })
    //Lambda作为函数的最后一个参数,可以把Lambda写在括号之外
    onlyif(true) {
         println("日志信息$it")
    }
}
//Unit表示无返回值,类似于Java的Void
fun onlyif(isDebug: Boolean, a: (String) -> Unit) {
    if (isDebug) {
        a.invoke("adada")
    }
}

inline修饰符

lambda闭包在编译时会被编译成Function接口对象,因此在高阶函数调用时,会产生很多临时的无用对象。可以使用inline关键字来修饰高阶函数,在编译时,将函数的代码拷贝到调用的地方,减少不必要对象的创建。

fun main(args: Array<String>) {
    for (i in 0..10) {
        sum(1, 2) { println("Result is: $it") }
    }
}

inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    return r
}

反编译为java

public static final void main(@NotNull String[] args) {
   //...
   int var1 = 0;

  for(byte var2 = 10; var1 <= var2; ++var1) {
     byte a$iv = 1;
     int b$iv = 2;
     int r$iv = a$iv + b$iv;
     String var9 = "Result is: " + r$iv;
     System.out.println(var9);
  }
}

noinline

如果传递给 inline 函数的 lambda,有 return 语句,那么会导致闭包的调用者也返回。这个时候就需要使用noinline关键字,声明 inline 函数的形参中,不希望内联的 lambda

  inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int {
      val r = a + b
      lambda.invoke(r)
      lambda2.invoke(r)
      return r
  }

  fun main(args: Array<String>) {
      sum(1, 2,
              { println("Result is: $it") },
              { println("Invoke lambda2: $it") 
                return  
              }
      )
  }

crossinline

表明 inline 函数的形参中的 lambda 不能有 return语句,这样可以避免使用inline时,lambda中的return语句影响程序流程

inline fun sum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    return r
}

fun main(args: Array<String>) {
    sum(1, 2) {
        println("Result is: $it")
        return  // 编译错误: return is not allowed here
    }
}