JavaRush /Java 博客 /Random-ZH /Lambda 表达式及示例

Lambda 表达式及示例

已在 Random-ZH 群组中发布
Java最初是一种完全面向对象的语言。除了原始类型之外,Java 中的一切都是对象。甚至数组也是对象。每个类的实例都是对象。不存在单独定义函数的可能性(在类之外 -近似翻译)。并且无法将方法作为参数传递或返回方法体作为另一个方法的结果。就像那样。但这是在 Java 8 之前的事。 Lambda 表达式及示例 - 1从古老的 Swing 时代开始,当需要将某些功能传递给某些方法时,就必须编写匿名类。例如,添加事件处理程序如下所示:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
这里我们要向鼠标事件监听器添加一些代码。我们定义了一个匿名类MouseAdapter并立即从中创建了一个对象。通过这种方式,我们已经将附加功能传递到了addMouseListener. 简而言之,在Java中通过参数传递一个简单的方法(功能)并不容易。这一限制迫使 Java 8 开发人员在语言规范中添加 Lambda 表达式等功能。

为什么Java需要Lambda表达式?

从一开始,Java 语言就没有太大的发展,除了注解、泛型等。首先,Java 始终保持面向对象。使用过 JavaScript 等函数式语言后,我们就能理解 Java 是如何严格面向对象和强类型的。Java 中不需要函数。在 Java 世界中无法单独找到它们。在函数式编程语言中,函数脱颖而出。它们独立存在。您可以将它们分配给变量并将它们通过参数传递给其他函数。JavaScript 是函数式编程语言的最佳示例之一。您可以在 Internet 上找到详细介绍 JavaScript 作为函数式语言的优点的好文章。函数式语言拥有 Closure 等强大的工具,与传统的应用程序编写方法相比,它具有许多优势。闭包是一个附有环境的函数 - 一个存储对该函数的所有非局部变量的引用的表。在Java中,可以通过Lambda表达式来模拟闭包。当然,闭包和 Lambda 表达式之间存在差异,而且差异不小,但 Lambda 表达式是闭包的一个很好的替代方案。在他讽刺而有趣的博客中,Steve Yegge 描述了 Java 世界如何与名词(实体、对象 -大约翻译)严格联系在一起。如果您还没有读过他的博客,我推荐您阅读。他用有趣的方式描述了 Lambda 表达式被添加到 Java 中的确切原因。Lambda 表达式为 Java 带来了长期以来缺失的功能。Lambda 表达式就像对象一样为语言带来功能。虽然这不是 100% 正确,但您可以看到 Lambda 表达式虽然不是闭包,但提供了类似的功能。在函数式语言中,lambda 表达式是函数;但在 Java 中,lambda 表达式由对象表示,并且必须与称为函数式接口的特定对象类型关联。接下来我们就来看看它是什么。Mario Fusco 的文章《Why we need Lambda Expression in Java》详细描述了为什么所有现代语言都需要闭包功能。

Lambda 表达式简介

Lambda 表达式是匿名函数(对于 Java 来说可能不是 100% 正确的定义,但它带来了一些清晰度)。简单来说,这是一个没有声明的方法,即 没有访问修饰符,返回值和名称。简而言之,它们允许您编写一个方法并立即使用它。它在一次性方法调用的情况下特别有用,因为 减少了声明和编写方法所需的时间,而无需创建类。Java 中的 Lambda 表达式通常具有以下语法(аргументы) -> (тело)。例如:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
以下是真实 Lambda 表达式的一些示例:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Lambda 表达式的结构

我们来研究一下lambda表达式的结构:
  • Lambda 表达式可以有 0 个或多个输入参数。
  • 参数的类型可以明确指定,也可以从上下文中获取。例如( int a)可以写成这样( a)
  • 参数括在括号内并用逗号分隔。例如 ( a, b) 或 ( int a, int b) 或 ( String a, int b, float c)
  • 如果没有参数,则需要使用空括号。例如() -> 42
  • 当只有一个参数时,如果没有显式指定类型,则括号可以省略。例子:a -> return a*a
  • Lambda 表达式的主体可以包含 0 个或多个表达式。
  • 如果主体由单个语句组成,则可以不将其括在花括号中,并且可以在不使用关键字 的情况下指定返回值return
  • 否则,需要大括号(代码块),并且必须在末尾使用关键字指定返回值return(否则返回类型将为void)。

什么是函数式接口

在Java中,Marker接口是没有声明方法或字段的接口。换句话说,令牌接口是空接口。类似地,函数式接口是仅声明一个抽象方法的接口。 java.lang.Runnable是一个函数式接口的例子。它只声明一种方法void run()。还有一个界面ActionListener- 也很实用。以前,我们必须使用匿名类来创建实现函数接口的对象。有了 Lambda 表达式,一切都变得更加简单。每个 lambda 表达式都可以隐式绑定到某个函数接口。例如,您可以创建对Runnable接口的引用,如以下示例所示:
Runnable r = () -> System.out.println("hello world");
当我们不指定函数接口时,这种转换总是隐式完成的:
new Thread(
    () -> System.out.println("hello world")
).start();
在上面的示例中,编译器自动创建一个 lambda 表达式作为Runnable类构造函数中接口的实现Threadpublic Thread(Runnable r) { }。以下是 lambda 表达式和相应函数接口的一些示例:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
@FunctionalInterfaceJava 8 根据 Java 语言规范添加的 注释检查声明的接口是否有效。此外,Java 8 还包含许多现成的函数接口,可与 Lambda 表达式一起使用。@FunctionalInterface如果声明的接口不起作用,则会抛出编译错误。以下是定义函数式接口的示例:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
正如定义所示,函数式接口只能有一个抽象方法。如果您尝试添加另一个抽象方法,您将收到编译错误。例子:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
错误
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
结论:
Worker вызван через анонимный класс
Worker вызван через Lambda
这里我们定义了自己的函数式接口并使用了 lambda 表达式。该方法execute()能够接受 lambda 表达式作为参数。

Lambda 表达式的示例

理解 Lambda 表达式的最佳方法是看几个示例: 流Thread可以通过两种方式初始化:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Java 8 中的事件管理也可以通过 Lambda 表达式来完成。以下是将事件处理程序添加ActionListener到 UI 组件的两种方法:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
显示给定数组的所有元素的简单示例。请注意,使用 lambda 表达式的方法不止一种。下面我们使用箭头语法以通常的方式创建一个 lambda 表达式,并且我们还使用双冒号运算符(::),它在 Java 8 中将常规方法转换为 lambda 表达式:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
在下面的示例中,我们使用函数式接口Predicate来创建测试并打印通过该测试的项目。通过这种方式,您可以将逻辑放入 lambda 表达式中并基于它执行操作。
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
结论:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
通过修改 Lambda 表达式,您可以显示列表中每个元素的平方。请注意,我们正在使用该方法stream()将常规列表转换为流。Java 8 提供了一个很棒的类Stream( java.util.stream.Stream)。它包含大量可以使用 lambda 表达式的有用方法。我们将 lambda 表达式传递x -> x*x给 method map(),该方法将其应用于流中的所有元素。之后我们用来forEach打印列表的所有元素。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
给定一个列表,您需要打印列表中所有元素的平方和。Lambda 表达式让您只需编写一行代码即可实现此目的。本例使用卷积(约简)方法reduce()。我们使用一种方法map()对每个元素求平方,然后使用一种方法reduce()将所有元素折叠成一个数字。
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

Lambda表达式和匿名类的区别

主要区别在于关键字的使用this。对于匿名类,“ ”关键字this表示匿名类的对象,而在 lambda 表达式中,“ this”表示使用 lambda 表达式的类的对象。另一个区别是它们的编译方式。Java 编译 lambda 表达式并将其转换为private类方法。这使用了Java 7 中引入的invokedynamic指令,用于动态方法绑定。Tal Weiss 在他的博客中描述了 Java 如何将 lambda 表达式编译为字节码

结论

Mark Reinhold(Oracle 首席架构师)称 Lambda 表达式是编程模型中有史以来最重大的变化 - 甚至比泛型更重要。他一定是对的,因为…… 它们为 Java 程序员提供了每个人都在等待的函数式编程语言功能。除了虚拟扩展方法等创新之外,Lambda 表达式还允许您编写非常高质量的代码。我希望这篇文章能让您深入了解 Java 8。祝您好运:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION