Java最初是一种完全面向对象的语言。除了原始类型之外,Java 中的一切都是对象。甚至数组也是对象。每个类的实例都是对象。不存在单独定义函数的可能性(在类之外 -近似翻译)。并且无法将方法作为参数传递或返回方法体作为另一个方法的结果。就像那样。但这是在 Java 8 之前的事。 从古老的 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
类构造函数中接口的实现Thread
:public 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 };
@FunctionalInterface
Java 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 表达式编译为字节码
GO TO FULL VERSION