了解如何在Java程序中使用lambda表达式和函数式编程技术
![2319993132634aa79084409029a96710.png](https://i-blog.csdnimg.cn/blog_migrate/ae5d8e2a90323fe22a8de919e0333ff6.jpeg)
在Java SE 8之前,通常使用匿名类将功能传递给方法。这种做法混淆了源代码,使其难以理解。Java 8通过引入lambda消除了这个问题。本教程首先介绍lambda语言功能,然后提供有关使用lambda表达式以及目标类型进行函数编程的更详细的介绍。您还将学习lambda如何与范围,局部变量,thisand super关键字以及Java异常交互。
请注意,本教程中的代码示例与JDK 12兼容。
自己发现类型
在本教程中,我不会介绍您以前没有学过的任何非lambda语言功能,但是我将通过本系列之前没有讨论过的类型来演示lambda。一个例子是java.lang.Math类。我将在以后的Java 101教程中介绍这些类型。现在,我建议阅读JDK 12 API文档以了解有关它们的更多信息。
下载获取代码下载本教程中示例应用程序的源代码。由Jeff Friesen为JavaWorld创建。
Lambdas:入门
甲lambda表达式(拉姆达)描述了一个代码块(一匿名功能可以传递到构造或方法用于后续执行)。构造函数或方法接收lambda作为参数。考虑以下示例:
[ 在这个由12部分组成的综合课程中,从入门概念到高级设计模式学习Java!]
() -> System.out.println("Hello")
此示例标识用于将消息输出到标准输出流的lambda。从左到右,()标识lambda的形式参数列表(示例中没有参数),->表示该表达式是lambda,并且System.out.println("Hello")是要执行的代码。
Lambda简化了功能性接口的使用,它们是带注释的接口,每个接口都精确地声明一个抽象方法(尽管它们也可以声明默认,静态和私有方法的任意组合)。例如,标准类库提供java.lang.Runnable具有单个抽象void run()方法的接口。该功能接口的声明如下所示:
@FunctionalInterfacepublic interface Runnable{ public abstract void run();}
类库注释Runnable与@FunctionalInterface,这是的一个实例java.lang.FunctionalInterface注释类型。FunctionalInterface用于注释在lambda上下文中使用的那些接口。
Lambda没有明确的接口类型。相反,编译器使用周围的上下文来推断指定lambda时要实例化的功能接口-lambda 绑定到该接口。例如,假设我指定了以下代码片段,它将前一个lambda作为参数传递给java.lang.Thread类的Thread(Runnable target)构造函数:
new Thread(() -> System.out.println("Hello"));
编译器确定将lambda传递给它,Thread(Runnable r)因为这是唯一满足lambda的构造Runnable函数:是一个函数接口,lambda的空形式参数列表()与的空参数列表匹配run(),并且返回类型(void)也一致。Lambda绑定到Runnable。
清单1将源代码提供给一个小型应用程序,让您可以使用此示例。
清单1. LambdaDemo.java(版本1)
public class LambdaDemo{ public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); }}
编译清单1(javac LambdaDemo.java)并运行应用程序(java LambdaDemo)。您应该观察以下输出:
Hello
Lambda可以大大简化您必须编写的源代码数量,并且还可以使源代码更易于理解。例如,如果没有lambda,您可能会指定清单2的更详细的代码,该代码基于实现的匿名类的实例Runnable。
清单2. LambdaDemo.java(版本2)
public class LambdaDemo{ public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; new Thread(r).start(); }}
编译此源代码后,运行该应用程序。您将发现与先前显示的输出相同的输出。
Lambdas和Streams API
除了简化源代码外,lambda在Java的面向功能的Streams API中也起着重要作用。它们描述了传递给各种API方法的功能单元。
深入Java Lambda
为了有效地使用lambda,您必须了解lambda表达式的语法以及目标类型的概念。您还需要了解lambda如何与范围,局部变量,thisand和super关键字以及异常进行交互。我将在以下各节中介绍所有这些主题。
lambda如何实现
Lambda是根据Java虚拟机的invokedynamic指令和java.lang.invokeAPI来实现的。观看视频“ Lambda:深入了解”以了解Lambda体系结构。
Lambda语法
每个lambda都遵循以下语法:
( formal-parameter-list ) -> { expression-or-statements }
的formal-parameter-list是逗号分隔的形式参数,它必须在运行时功能接口的一个抽象方法的参数相匹配的列表。如果省略它们的类型,则编译器将从使用lambda的上下文中推断出这些类型。考虑以下示例:
(double a, double b) // types explicitly specified(a, b) // types inferred by compiler
Lambdas和var
从Java SE 11开始,您可以将类型名称替换为var。例如,您可以指定(var a, var b)。
您必须为多个或没有形式参数指定括号。但是,在指定单个形式参数时,可以省略括号(尽管不必这样做)。(这仅适用于参数名称-当还指定类型时,必须使用括号。)请考虑以下其他示例:
x // parentheses omitted due to single formal parameter(double x) // parentheses required because type is also present() // parentheses required when no formal parameters(x, y) // parentheses required because of multiple formal parameters
的formal-parameter-list后面是->令牌,其后是expression-or-statements-表达式或语句块(称为lambda的主体)。与基于表达式的主体不同,必须将基于语句的主体置于open({)和close(})大括号字符之间:
(double radius) -> Math.PI * radius * radiusradius -> { return Math.PI * radius * radius; }radius -> { System.out.println(radius); return Math.PI * radius * radius; }
第一个示例的基于表达式的lambda主体不必放在括号之间。第二个示例将基于表达式的主体转换为基于语句的主体,return必须在其中指定以返回表达式的值。最后的示例演示了多个语句,没有括号就无法表达。
Lambda实体和分号
请注意;,在前面的示例中不存在分号()。在每种情况下,lambda主体都不会以分号终止,因为lambda并非语句。但是,在基于语句的lambda主体中,每个语句必须以分号结尾。
清单3提供了一个简单的应用程序,该应用程序演示了lambda语法;请注意,此清单建立在前两个代码示例的基础上。
清单3. LambdaDemo.java(版本3)
@FunctionalInterfaceinterface BinaryCalculator{ double calculate(double value1, double value2);}@FunctionalInterfaceinterface UnaryCalculator{ double calculate(double value);}public class LambdaDemo{ public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); }}
清单3首先介绍BinaryCalculator和UnaryCalculator接口,它们的calculate()方法分别对两个输入参数或单个输入参数执行计算。此清单还引入了一个LambdaDemo类,该类的main()方法演示了这些功能接口。
功能接口在static double calculate(BinaryCalculator calc, double v1, double v2)和static double calculate(UnaryCalculator calc, double v)方法中进行了演示。Lambda将代码作为数据传递给这些方法,这些方法作为BinaryCalculator或UnaryCalculator实例接收。
编译清单3并运行该应用程序。您应该观察以下输出:
18 + 36.5 = 54.50000089 / 2.9 = 30.689655-89 = -89.00000018 * 18 = 324.000000
目标类型
Lambda与隐式目标类型关联,该目标类型标识Lambda绑定到的对象的类型。目标类型必须是从上下文推断出的功能接口,它将lambda限制为出现在以下上下文中:
- 变量声明
- 分配
- 退货声明
- 数组初始化器
- 方法或构造函数参数
- λ体
- 三元条件表达式
- 演员表
清单4提供了一个演示这些目标类型上下文的应用程序。
清单4. LambdaDemo.java(版本4)
import java.io.File;import java.io.FileFilter;import java.nio.file.Files;import java.nio.file.FileSystem;import java.nio.file.FileSystems;import java.nio.file.FileVisitor;import java.nio.file.FileVisitResult;import java.nio.file.Path;import java.nio.file.PathMatcher;import java.nio.file.Paths;import java.nio.file.SimpleFileVisitor;import java.nio.file.attribute.BasicFileAttributes;import java.security.AccessController;import java.security.PrivilegedAction;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.concurrent.Callable;public class LambdaDemo{ public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i < files.length; i++) System.out.println(files[i]); // Target type #4: array initializer FileSystem fs = FileSystems.getDefault(); final PathMatcher matchers[] = { (path) -> path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor visitor; visitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i < matchers.length; i++) { if (matchers[i].matches(name)) System.out.printf("Found matched file: '%s'.%n", file); } return FileVisitResult.CONTINUE; } }; Files.walkFileTree(Paths.get("."), visitor); // Target type #5: method or constructor arguments new Thread(() -> System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); }}
若哪里有错误或您有更好的方法,请留言指出~
若要获取更多Java干货知识,请关注我~
谢谢~