了解如何在Java程序中使用lambda表达式和函数式编程技术
在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()方法的接口。该功能接口的声明如下所示:
@FunctionalInterface
public 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 * radius
radius -> { 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)
@FunctionalInterface
interface BinaryCalculator
{
double calculate(double value1, double value2);
}
@FunctionalInterface
interface 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.500000
89 / 2.9 = 30.689655
-89 = -89.000000
18 * 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干货知识,请关注我~
谢谢~