在JavaSE8之前,匿名类通常用于将功能传递给方法。这种做法混淆了源代码,使其更难理解。Java8通过引入lambdas消除了这个问题。本教程首先介绍lambda语言功能,然后详细介绍使用lambda表达式和目标类型进行函数式编程。您还将了解lambda如何与作用域、局部变量、this和super关键字以及Java异常交互。
请注意,本文中的代码示例与JDK 12兼容。
Lambdas: A primer
lambda表达式(lambda)描述可传递给构造函数或方法以供后续执行的代码块(匿名函数)。构造函数或方法接收lambda作为参数。考虑下面的例子:
() -> 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();
}
类库用@functionanterface
注释Runnable
,后者是java.lang.functionanterface
注释类型的实例。FunctionInterface
用于注释将在lambda上下文中使用的接口。
lambda没有显式接口类型。相反,编译器使用周围的上下文来推断在指定lambda时实例化哪个函数接口——lambda绑定到该接口。例如,假设我指定了以下代码片段,它将前面的lambda作为参数传递给java.lang.Thread
类的Thread(Runnable target)
构造函数:
new Thread(() -> System.out.println("Hello"));
编译器确定lambda正在传递给Thread(Runnable r)
,因为这是满足lambda的唯一构造函数:Runnable
是函数接口,lambda的空形式参数list ()
与run()
的空参数列表匹配,并且返回类型(void
)也一致。lambda必须是可运行的。
清单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
Lambdas可以极大地简化您必须编写的源代码量,还可以使源代码更易于理解。例如,如果没有lambdas,您可能会指定清单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();
}
}
编译此源代码后,运行应用程序。您将发现与前面所示相同的输出。
Java lambdas深入研究
为了有效地使用lambda,您必须理解lambda表达式的语法以及目标类型的概念。您还需要了解lambda如何与作用域、局部变量、this
和super
关键字以及异常交互。我将在接下来的章节中介绍所有这些主题。
Lambda语法
每个lambda都符合以下语法:
( formal-parameter-list ) -> { expression-or-statements }
形式参数列表是以逗号分隔的形式参数列表,它必须在运行时匹配函数接口的单个抽象方法的参数。如果省略它们的类型,编译器将从使用lambda的上下文中推断出这些类型。考虑下面的例子:
(double a, double b) // types explicitly specified
(a, b) // types inferred by compiler
Lambdas和var
从JavaSE11开始,您可以用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
形式参数列表后面是->
标记,后面是表达式或语句——表达式或语句块(称为lambda体)。与基于表达式的实体不同,基于语句的实体必须放置在打开({
)和关闭(}
)大括号字符之间:
(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
函数接口,其calculator()
方法分别对两个输入参数或单个输入参数执行计算。该清单还介绍了一个LambdaDemo
类,其main()
方法演示了这些函数接口。
静态双计算(BinaryCalculator calc,double v1,double v2
)和静态双计算(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限制在以下上下文中出现:
- 变量声明
- 分配
- 返回语句
- 数组初始值设定项
- 方法或构造函数参数
- 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<Path> visitor;
visitor = new SimpleFileVisitor<Path>()
{
@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<Runnable> callable = () -> () ->
System.out.println("called");
callable.call().run();
// Target type #7: ternary conditional expression
boolean ascendingSort = false;
Comparator<String> cmp;
cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2)
: (s1, s2) -> s2.compareTo(s1);
List<String> 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<String>) ()
-> System.getProperty("user.name"));
System.out.println(user);
}
static FileFilter getFilter(String ext)
{
return (pathname) -> pathname.toString().endsWith(ext);
}
}
原文地址:https://www.infoworld.com/article/3452018/get-started-with-lambda-expressions.html
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2373.html
暂无评论