Lambda 表达式简介

原文: https://javabeginnerstutorial.com/core-java-tutorial/introduction-lambda-expressions/

在本文中,我将向您简要介绍 Java 8 的一项新功能:lambda 表达式。 我将向您展示什么是 lambda 表达式以及如何利用这一新功能。

什么是 lambda 表达式?

Lambda 表达式类似于其他编程语言中的闭包-或 Java 本身中的匿名函数或内部类。

Lambda 表达式希望利用匿名类的用法。 您可能还记得将操作监听器注册到图形用户界面中的按钮的方式:

  1. JButton clickMeButton = new JButton("Click me!");
  2. clickMeButton.addActionListener(new ActionListener(){
  3. @Override public void actionPerformed(ActionEvent event){
  4. System.out.println("You clicked me!");
  5. }
  6. });

是的,上面的代码向您展示了一个匿名内部类,该类仅用于实现ActionListener接口的唯一方法actionPerformed(ActionEvent)。 而且这很笨拙—即使现代的 IDE 可以帮助您完成代码填充以填充方法的主体。

但是,使用 lambda 表达式,您可以像下面这样简单地实现此方法:

  1. JButton clickMeButton = new JButton("Click me!");
  2. clickMeButton.addActionListener(event -> System.out.println("You clicked me!"));

这样好多了,不是吗? 现在该深入了解如何构建自己的 lambda 表达式了。

Lambda 表达式具有以下语法:

  1. parameters -> expression body

lambda 表达式的关键是->箭头符号。

让我们看一个基本的示例:

  1. (int x) -> { return x * x; };

还有其他一些特征可以简化 lambda 表达式的使用:

  • 如果仅需要一个参数,则括号在参数周围是可选的
  • 参数的类型声明是可选的
  • return语句是可选的
  • 表达式主体周围的花括号是可选的—如果表达式主体包含单个语句

因为我们的简单表达式符合上述所有四个要点,所以我们可以像这样覆盖 lambda 表达式:

  1. x -> x * x;

这使事情更具可读性。

函数式接口

Lambda 表达式可用于实现所谓的函数式接口的方法。 较新的接口可以带有注解@FunctionalInterface,以指示此接口是函数式接口-但是,每个接口都可以视为仅具有一个抽象方法的函数式接口。因此,即使接口没有被非正式地标记为FunctionalInterface,也可以使用 lambda 表达式替换/实现ActionListeneraddActionListener

默认方法不具有抽象性,因为它们具有实现。 如果接口在不提供实现的情况下覆盖了java.lang.Object的方法,则该实现也不会被视为抽象,因为该实现将继承java.lang.Object类的实现。 让我们来看一个示例:

  1. /**
  2. * @author GHajba
  3. *
  4. */
  5. public interface ExampleFunctionalInterface {
  6. /**
  7. * @return a name
  8. */
  9. String getName();
  10. /**
  11. * prints a greeting
  12. */
  13. default void printGreeting() {
  14. System.out.println("Hello " + getName() + "!");
  15. }
  16. /**
  17. *
  18. * @return the string representation of this object
  19. */
  20. @Override
  21. String toString();
  22. }

即使我们不添加注解,上面的接口也是一个函数式接口。 现在,我们创建一个 lambda 并使用它。 请记住,我们的 lambda 必须仅实现getName()函数,该函数不需要任何参数并返回String

  1. final ExampleFunctionalInterface example = () -> "GHajba"; // implements the getName() function
  2. example.printGreeting();
  3. System.out.println(example); // this calls example.toString() implicitly

如果运行示例,我们将得到与以下结果类似的结果:

  1. Hello GHajba!
  2. ChangeCalculator$$Lambda$1/[[email protected]](/cdn-cgi/l/email-protection)

如您所见,默认方法可以与实现的 lambda 一起正常工作,并且可以从java.lang.Object调用toString,因为不存在 lambda 表达式中的直接实现。

更多示例

了解了 lambda 表达式后,我们来看一些示例,在这些示例中我们可以使用它们来改进现有或新代码。

因为 lambda 表达式是匿名函数或内部类,所以我们可以用它们替换对现有匿名函数的调用。 就像 GUI 应用的动作监听器或Runnable接口的本地实现一样。

  1. final Runnable runnable = new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("Hello lambdas?");
  5. }
  6. };
  7. runnable.run();

上面的示例使用匿名类实现Runnable接口。 现在来看看这个简单的“你好 lambdas”吗? 输出这是要做的一些工作。

  1. final Runnable runnable_lambdas = () -> System.out.println("Hello lambdas!");
  2. runnable_lambdas.run();

上面的代码示例使用 lambda 表达式,并且做完全相同的事情:它输出字符串“Hello lambdas!”。 到控制台-但是无需创建匿名类来实现Runnable接口。 自然,此代码仅适用于函数式接口(只有一种方法的接口)。

如您所见,也可以创建无参数的 lambda。 为此,您需要提供空括号作为参数,以表明没有任何内容。

除此之外,lambda 表达式也是闭包。 这意味着它们是可以保存在变量中并在以后重用的函数。 如果我们看一下上一节中的示例,我们可以将其保存到一个名为square的变量中,并在以后重用:

  1. IntUnaryOperator square = x -> x * x;

要使用它,我们可以直接调用它,也可以在某些流表达式中使用它,例如map

  1. System.out.println(square.applyAsInt(128));
  2. System.out.println("------");
  3. IntStream.rangeClosed(1, 10).map(square).forEach(System.out::println);

上面的代码片段的结果是这样的:

  1. 16384
  2. ------
  3. 1
  4. 4
  5. 9
  6. 16
  7. 25
  8. 36
  9. 49
  10. 64
  11. 81
  12. 100

柯里化

柯里化是函数部分应用的名称。 这就是说,您有一个函数,该函数需要多个参数,但是大多数参数已经为人所知,并且将保持不变-只有一个会发生变化。 而且,除了在调用函数时提供每个参数之外,还可以部分咖喱函数并设置一些默认参数。

让我们看一下以下示例:

  1. public static Function<Integer, Function<Integer, Integer>> multiply() {
  2. return x -> y -> x * y;
  3. }

这段代码创建了一个简单的函数,将两个Integer类型的变量相乘。 因为返回的函数需要两个参数(x 和 y),所以我们可以创建这些函数的当前版本并在以后使用它们:

  1. final Function<Integer, Integer> two_times = multiply().apply(2);
  2. final Function<Integer, Integer> three_times = multiply().apply(3);

现在,我们对每个函数都应用了一个(变量)变量。 如果调用这些函数(应用第二个变量),我们可以看到结果表示已经应用的值:

  1. System.out.println(two_times.apply(3)); // prints 6
  2. System.out.println(three_times.apply(3)); // prints 9

每次自然地调用apply()有点麻烦,但是 Java 目前无法使其变得更好。 如果可以编写以下代码,则将是更好的语法:

  1. final Function<Integer, Integer> two_times = multiply()(2)
  2. System.out.println(two_times(3));

这目前尚不起作用,但是添加到 Java 语言中将是一个不错的语法糖。

总结

如果您要实现函数式接口而无需创建和实例化实现该接口的类,那么 Lambda 表达式功能强大-主要是因为您只需要一次(例如,用作按钮的ActionListener)。

我们研究了珂里化/函数部分应用,并讨论了一些功能将使其更易于在 Java 中使用,并希望在将来的版本中我们可以使用它。