Kotlin的Lambda闭包语法

Lambda 表达式是一种在现代编程语言中常见的特性,它可以用来创建匿名函数或代码块,使得将函数作为参数传递、简化代码以及实现函数式编程范式变得更加便捷。Lambda 表达式在函数式编程语言中得到广泛应用,也在诸如 Java 8 和 Kotlin 等主流编程语言中引入。

一、Java 中的 Lambda 表达式语法

在 Java 中,Lambda 表达式是从 Java 8 版本开始引入的一项重要特性,它允许我们以更简洁的方式创建匿名函数,从而在处理函数式编程、回调等场景中变得更加方便。Lambda 表达式的引入在 Java 中使得编写代码变得更加紧凑,同时也提供了更强大的函数式编程能力。

以下是 Java 中 Lambda 表达式的基本概念和语法:

java 复制代码
(parameters) -> { /* code */ }
  • parameters 是 Lambda 表达式的参数列表。参数的类型可以根据上下文进行类型推断。
  • -> 是 Lambda 表达式的箭头符号,分隔参数列表和函数体。
  • { /* code */ } 是 Lambda 表达式的函数体,可以是一行或多行代码。

Lambda 表达式通常用于函数式接口(Functional Interface)中,这是一个只包含一个抽象方法的接口。Lambda 表达式可以实现这个接口的抽象方法,从而将匿名函数传递给方法或函数。

以下是一个简单的示例,展示了如何在 Java 中使用 Lambda 表达式:

Java 复制代码
public class LambdaExample {
    public static void main(String[] args) {
        // Lambda 表达式作为参数传递给 Thread 的构造函数
        Thread thread = new Thread(() -> {
            System.out.println("Thread is running");
        });
        thread.start();
    }
}

在这个示例中,Lambda 表达式 () -> { System.out.println("Thread is running"); } 被传递给 Thread 类的构造函数,用作线程的任务(Runnable)。这样就避免了传统的匿名内部类的繁琐语法,使代码更加简洁。

需要注意的是,Lambda 表达式在 Java 中需要满足以下要求:

  • Lambda 表达式只能用于函数式接口中,该接口必须只包含一个抽象方法。
  • 如果接口有默认方法(default 关键字修饰的方法),那么它不会影响函数式接口的定义。
  • 在 Lambda 表达式中可以捕获 final 或 effectively final 的局部变量。

通过掌握 Java 中的 Lambda 表达式,你可以在代码中更自然地表达函数式概念,从而编写更简洁、高效的代码。

二、Kotlin 中的 Lambda 表达式语法

在 Kotlin 中,Lambda 表达式是一种强大的特性,允许你以简洁的方式创建匿名函数,从而在函数式编程、集合操作等场景中变得更加方便。Kotlin 的 Lambda 表达式语法非常灵活,与函数的声明和调用紧密结合,使得代码更加清晰和易读。

以下是 Kotlin 中 Lambda 表达式的基本语法和一些重要概念:

kotlin 复制代码
{ parameters -> /* code */ }

可以看出和 Java 的Lambda语法类似,只不过这个箭头是放在了大括号的里面,而不是外面。

  • parameters 是 Lambda 表达式的参数列表。参数的类型可以根据上下文进行类型推断。
  • -> 是 Lambda 表达式的箭头符号,分隔参数列表和函数体。
  • { /* code */ } 是 Lambda 表达式的函数体,可以是一行或多行代码。

Lambda 表达式通常用于函数式接口(Functional Interface)中,或者用于集合操作、高阶函数等情境中。

以下是一个简单的示例,展示了如何在 Kotlin 中使用 Lambda 表达式:

kotlin 复制代码
fun main() {
    val thread = Thread ({ -> println("Thread is running") })
    thread.start()
}

如果 Lambda 是函数的最后一个参数,可以将大括号放在小括号外面:

kotlin 复制代码
fun main() {
    val thread = Thread() { -> println("Thread is running") }
    thread.start()
}

如果函数只有一个参数且这个参数是 Lambda,则可以省略小括号:

kotlin 复制代码
fun main() {
    val thread = Thread { -> println("Thread is running") }
    thread.start()
}

如果 Lambda 表达式没有参数,你可以直接省略 -> 符号:

kotlin 复制代码
fun main() {
    val thread = Thread { println("Thread is running") }
    thread.start()
}

好了,就此我们看一下 Java 和 Kotlin 的 Lambda 表达式,如图所示:

三、声明一个 Kotlin 的 Lambda 闭包

Kotlin 的 Lambda 表达式本质上就是闭包,它是一种可以捕获并携带作用域中变量状态的函数。Lambda 闭包在 Kotlin 中具有以下特点:

  1. 捕获变量: Lambda 表达式可以在其作用域外捕获外部变量。这意味着 Lambda 表达式可以访问外部作用域中的变量,即使在该作用域已经结束的情况下。捕获的变量可以是 valvar,但在 Lambda 表达式中只能被读取,不能被修改(对于 val)。
  2. 记住状态: Lambda 表达式捕获的变量状态在闭包内是"记住"的,即使闭包被传递到其他函数或在不同上下文中执行,它仍然可以访问并使用这些变量。
  3. 隐式参数: 当 Lambda 表达式只有一个参数时,可以使用隐式参数 it 来代表这个参数。这使得代码更加简洁。
  4. 函数式编程: Lambda 闭包使得 Kotlin 可以支持函数式编程范式,使代码更加模块化、易读和易于测试。

Kotlin 的 Lambda 闭包(也称为 Lambda 表达式)具有以下基本格式:

kotlin 复制代码
val lambdaName: (parameters) -> returnType = { arguments ->
    // Lambda 表达式的主体
}

我们解释一下每个部分的含义:

  • val lambdaName: 这是 Lambda 表达式的变量名,你可以根据需要命名它。
  • (parameters) -> returnType: 这是 Lambda 表达式的类型,它指定了参数列表和返回类型。parameters 是参数列表,可以是一个或多个参数,用逗号分隔。returnType 是返回类型,指示 Lambda 表达式返回的值的类型。
  • arguments ->: 这是 Lambda 表达式的参数部分。arguments 是 Lambda 表达式的参数,你可以为参数指定名称。箭头 -> 分隔了参数和主体部分。
  • { /* Lambda 表达式的主体 */ }: 这是 Lambda 表达式的主体,包含了 Lambda 表达式的实际逻辑和操作。这部分代码会在 Lambda 表达式被调用时执行。

下面是一个简单的示例,演示如何声明一个 Kotlin 的 Lambda 闭包:

kotlin 复制代码
fun main() {
    person.invoke("danke")
    person("danke")
}

val person: (name: String) -> Unit = {
        name: String -> println("name is $name")
        // 可以省略Unit
}

在这个示例中,我们首先声明了一个名为 person 的变量,其值是一个 Lambda 表达式。这个 Lambda 表达式接受一个 String 类型的参数 name,并在闭包体内使用 $name 来将参数的值嵌入到字符串中。这个 Lambda 表达式就是 Lambda 闭包。

在 Kotlin 中,如果一个 Lambda 表达式没有返回值,可以使用 Unit 来表示。Unit 实际上是一个类型,类似于 Java 中的 void,但它是一个真正的类型,表示一个只有一个值的类型。

当 Lambda 表达式没有明确的返回值时,Kotlin 会默认将其推断为 Unit 类型。因此,你可以省略不写返回值类型。

接下来,在 main 函数中,我们通过两种方式来调用 person 这个闭包:

  • person.invoke("danke"):这里使用 invoke 函数来显式地调用闭包,并传递参数 "danke"
  • person("danke"):这是一种更简洁的方式,直接像调用函数一样调用闭包,并传递参数 "danke"

无论使用哪种方式,闭包都会执行,打印出 "name is danke"

上面示例展示了如何声明一个 Kotlin 的 Lambda 闭包,并且演示了如何调用闭包并传递参数。闭包在 Kotlin 中是一种非常强大的概念,允许在代码中以一种更函数式的方式操作数据和逻辑。

在 Kotlin 中,如果 Lambda 表达式的参数和返回类型可以从上下文中推断出来,你可以省略参数列表和返回类型的定义。那么我们改造一下上面声明的 Lambda 闭包,其中省略了参数列表和返回类型的定义:

kotlin 复制代码
val person = { name: String -> println("name is $name") }

在这个示例中,由于 Lambda 表达式的参数类型和返回类型可以从赋值操作的右侧(Lambda 表达式)推断出来,所以我们可以省略掉 (name: String) -> Unit 这部分。编译器会自动推断出参数类型为 String,返回类型为 Unit

这种简化的写法使代码更加简洁,但需要注意的是,当 Lambda 表达式的参数和返回类型不容易从上下文中推断出时,最好还是显式地声明它们,以增加代码的可读性和清晰性。

Kotlin 的 Lambda 闭包使得函数变得更具表达力和灵活性。它们可以用于各种情境,从集合操作到异步编程,从而提供了更多的编程选择和优雅的语法。

四、Kotlin 的 Lambda 的参数是有上限的

在 Kotlin 中,Lambda 表达式的参数数量是有上限的,这个上限与函数式接口的抽象方法数量有关。通常情况下,Lambda 表达式的参数数量最多为 22,因为 Kotlin 的标准库中提供了 22 个用于函数式编程的函数式接口(如 Function0Function22)。

这意味着你可以创建一个具有最多 22 个参数的 Lambda 表达式。如果我们尝试给 Lambda 表达式添加第 23 个参数的时候,例如:

kotlin 复制代码
fun main() {
    maxParams(
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1
    )
}

val maxParams =
    { p1: Int, p2: Int, p3: Int, p4: Int, p5: Int, p6: Int, p7: Int, p8: Int, p9: Int, p10: Int,
      p11: Int, p12: Int, p13: Int, p14: Int, p15: Int, p16: Int, p17: Int, p18: Int, p19: Int, p20: Int,
      p21: Int, p22: Int, p23: Int ->
        println("maxParams")
    }

看到最终运行这段代码就抛出一个异常:java.lang.NoClassNotFoundError:kotlin/Function23。为什么它最后报没有Function23这样的一个类呢?这是因为 Kotlin 的类在编译以后会被编译成 class 文件,Kotlin 的 lambda 在编译以后会被编译成一个匿名内部类,我们定义 lambda 表达式有 23 个参数,它也是一个匿名类,但是我们在的 Kotlin 的源码中,看到只定义了 22 个 Function,这个 Function 后面紧跟着数字,就表示有多少个参数。从零开始表示没有参数,一直到 22。我们可以看一下 Kotlin 源码:

因此参数数量超过 22 的 Lambda 表达式在调用时会引发错误。这是 Kotlin 的限制,Lambda 表达式的参数数量应该小于等于 22。

有没有办法解决?

如果我们给 Lambda 传了23个参数的时候,它就会直接报错了。那么这个问题能不能解决呢?当然也是可以的!就是我们手动去定义这个 Function23 这样的一个类。在定义这样一个类的时候,我们可能还会碰上问题。首先,我们一起来在代码中试一下。

kotlin 复制代码
// Function23.kt
package kotlin

/** A function that takes 23 arguments. */
public interface Function23<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, in P23, 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, p23: P23): R
}

首先创建Function23.kt,并将这个类的包名改成package kotlin。我们假设在 Kotlin 这个包下面创建一个 Function23 这样的一个类。那么它的类的声明是跟前面 Function22 完全一致,只是多了一个参数,我们就直接拷过来。这样我们就定义完成了 Function23。

然后我们再来运行这样的一段代码。看到编译器给我们报出来了一个错误,说只有Kotlin 的标准库才可以使用 Kotlin 这样的一个包名。而我们自己声明的类,是不能声明一个类的包名叫Kotlin的。

kotlin 复制代码
Kotlin: Only the Kotlin standard library is allowed to use the 'kotlin' package

那么,这个问题怎么解决呢?

大家回想一下 Kotlin 与 Java 是完全兼容的,如果我们不能以 Kotlin 的形式去声明一个类的话,那是不是可以用 Java 的形式去声明它?

kotlin 复制代码
/**
 * A function that takes 23 arguments.
 */
public interface Function23<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, P23, R> extends Function<R> {
    /**
     * Invokes the function with the specified arguments.
     */
    R 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, P23 p23);
}

所以咱们能够将这个Function23 申明为一个 Java类,并将它的包名设置为kotlin,这样就能够申明参数个数超过 22 的闭包了。

然而,使用拥有如此多参数的 Lambda 表达式往往会导致代码的可读性降低,而且实际情况下很少会需要这么多参数。在大多数情况下,Lambda 表达式的参数数量应该保持合理,以确保代码的清晰度和可维护性。

除了上限之外,Kotlin 还支持使用 vararg 关键字来定义一个接受可变数量参数的 Lambda 表达式,类似于普通函数的可变参数。(在上文中已经介绍过了)

总之,Kotlin 的 Lambda 表达式参数数量上限为 22,但实际中应该遵循良好的编程实践,保持适度的参数数量,以提高代码的可读性和可维护性。

五、Java 怎么调用 Kotlin 的 Lambda 闭包

Kotlin 文件 LambdaTest.kt

kotlin 复制代码
val person = { name: String -> println("name is $name") }

Java 文件 Main.java

kotlin 复制代码
import kotlin.Unit;
import kotlin.jvm.functions.Function1;

public class java8 {
    public static void main(String[] args) {
        Function1<String, Unit> person = LambdaTestKt.getPerson();
        person.invoke("danke");
    }
}

在这个示例中,我们在 Java 中通过 LambdaTestKt.getPerson() 方法来获取 Kotlin 文件中定义的 person Lambda 表达式实例,然后通过 person.invoke("danke") 调用该 Lambda 表达式。

这种方式可以在 Java 中调用 Kotlin 文件中的顶层 Lambda 表达式。

相关推荐
蓝桉802几秒前
图片爬取案例
开发语言·数据库·python
逸狼6 分钟前
【JavaEE进阶】Spring DI
java·开发语言
yonuyeung10 分钟前
代码随想录算法【Day54】
java·数据结构·算法
敲上瘾16 分钟前
基础dp——动态规划
java·数据结构·c++·python·算法·线性回归·动态规划
生产队队长23 分钟前
ThinkPHP:配置Redis并使用
android·数据库·redis
踏雪羽翼27 分钟前
android 差值器的使用
android
my_styles35 分钟前
2025-alibaba-Sentinel组件
java·开发语言·sentinel
Dongwoo Jeong37 分钟前
类型系统下的语言分类与类型系统基础
java·笔记·python·lisp·fortran·type
肖帆咪38 分钟前
deepseek自动化代码生成
java·ai·自动化·ai编程·deepseek