引言
在这篇文章,我们将学习 Lambda 表达式以及 Lambda 表达式与函数式接口的使用,泛型函数式接口,并演示流式传输 API。
Lambda 表达式是在 Java 8 中首次引入的,它的主要目的是提高编程语言的表达能力,简化冗余代码。
不过,在进入深入学习匿名函数之前,我们首先需要了解函数式接口。
什么是函数式接口?
如果一个 Java 接口包含一个且只有一个抽象方法,那么它被称为函数式接口。这样只有有一个方法规定了接口的预期结果类型。
示例 1:在 Java 中定义一个函数式接口
@FunctionalInterface
public interface DemoInterface {
// 单一抽象方法
Integer getValue();
}
在上面的例子中,接口 DemoInterface 只有一个抽象方法 getValue()。因此,它是一个功能接口。 在这里,我们使用了注解 @FunctionalInterface
,此注解的作用是强制 Java 编译器指示此接口为函数式接口。
在 Java 7 中,函数式接口被认为是 Single Abstract Method 或 SAM 类型,在 Java 7中,SAM通常用匿名类来实现。
示例 2:在 Java 中使用匿名类实现 SAM
public class FunctionInterfaceTest {
public static void main(String[] args) {
// 匿名类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我刚刚实现了 Runnable 的函数接口。");
}
}).start();
}
}
输出:
我刚刚实现了 Runnable 的函数接口。
在这里,我们可以将匿名类传递给方法。这有助于在 Java 7 中使用更少的代码编写程序。但是,语法仍然很困难,并且需要大量冗余的代码行。
Java 8 进一步扩展了 SAM 的功能。由于我们知道函数式接口只有一个方法,因此在将其作为参数传递时不需要定义该方法的名称。 Lambda 表达式恰好可以做到这一点。
Lambda 表达式简介
Lambda 表达式本质上是一个匿名或未命名的方法。Lambda 表达式不会自行执行。相反,它用于实现由函数式接口定义的方法。
如何在 Java 中定义 Lambda 表达式?
以下是我们如何在 Java 中定义 Lambda 表达式。
(参数集合) -> { Lambda 结构体 }
使用的新运算符 ->
称为 箭头运算符
或 Lambda 运算符
。或许语法可能说不太清楚。让我们体验一些示例:
假设,我们有一个这样的方法:
String getHubUrl() {
return "https://github.com";
}
我们可以使用 Lambda 表达式将此方法编写为:
() -> "https://github.com";
这里,该方法没有任何参数。因此,运算符的左边包括一个空参数。右边是λ主体,指定λ表达式的动作。在这种情况下,它返回值 https://github.com
。
Lambda 方法体的类型
在 Java 中,Lambda 主体有两种类型。
1. 单一表达式的主体
() -> System.out.println("Lambdas test.");
2. 由代码块组成的主体
() -> {
String sameSexUrl = "https://github.com";
return sameSexUrl;
};
这种类型的 Lambda 主体称为代码块。代码块允许 ambda 体包含多个语句。这些语句包含在大括号内,必须在大括号后添加一个分号。
笔记:对于块体,如果代码块返回值,则需要有一个 return 返回语句。但是,单一表达式主体不需要 return 语句。
示例 3: Lambda 表达式
让我们编写一个使用 Lambda 表达式返回 一个网址
的 Java 程序。
如前面提到的,一个 Lambda 表达式并不能自行执行。确切的说,它构成了函数式接口所定义的抽象方法的实现。
所以,我们需要先定义一个函数式接口:
@FunctionalInterface
public interface DemoInterface {
// 单一抽象方法
String getUrl();
}
public class DemoLambda {
public static void main(String[] args) {
// 声明对 DemoInterface 的引用
DemoInterface demoInterface;
// Lambda 表达式
demoInterface = () -> "https://www.github.com";
System.out.println("同行交流网站: " + demoInterface.getUrl());
}
}
输出:
同行交流网站: https://www.github.com
- 在上述的例子中,我们创建了一个名为
DemoInterface
的函数式接口。它包含一个名为getUrl()
的抽象方法。 - 在
DemoLambda
类中,我们声明了对DemoInterface
的引用。请注意,我们可以声明接口的引用,但不能实例化接口。比如:// 会抛出错误 DemoInterface ref = new DemoInterface(); // 正确编译 DemoInterface ref;
然后我们为引用分配了一个单一的 Lambda 表达式。
demoInterface = () -> "https://www.github.com";
最后,我们使用引用接口调用方法
getUrl()
,在如下时刻:
System.out.println("同行交流网站: " + demoInterface.getUrl());
带参数的 Lambda 表达式
到目前为止,我们已经创建了没有任何参数的 Lambda 表达式。但是,与调用方法类似,Lambda 表达式也可以有参数。例如:
(a,b) -> a + b
如上代码,括号内的变量 a 与 b 是传递给 Lambda 表达式的参数。 Lambda 主体接受参数并对它们进行相加。
示例 4:使用带参数的 Lambda 表达式
import java.util.List; @FunctionalInterface public interface ParamInterface { // 抽象方法 List<String> getStrList(String str); } import java.util.Arrays; public class ParamMain { public static void main(String[] args) { // 声明对 ParamInterface 的引用 // 将 lambda 表达式分配给引用 ParamInterface ref = (str) -> { String[] array = str.split("-"); return Arrays.asList(array); }; // 调用接口的方法 System.out.println(ref.getStrList("l-m-b-d-a").toString()); } }
输出:
[l, m, b, d, a]
通用函数式接口
到现在为止,我们使用的是只接受一种类型的值的函数式接口。比如说:
@FunctionalInterface public interface DemoInterface { // 单一抽象方法 String getUrlByText(String text); }
上面的功能接口只接受
String
类型并返回String
类型。但是,我们可以使这个函数接口通用化,这样就可以接受任何数据类型。如果你对泛型不确定,请先忽略,之后搜索Java泛型
学习下后再看也不迟。示例5:泛型函数式接口与 Lambda 表达式
@FunctionalInterface public interface GenericsInterface<T> { // 泛型方法 T func(T t); } public class GenericsMain { public static void main(String[] args) { // 声明对 GenericsInterface 的一个引用, 针对字符进行操作 // 为其分配一个 lambda 表达式 GenericsInterface<String> ref = (str) -> str.toUpperCase(); System.out.println("lambda 转为大写: " + ref.func("lambda")); // 声明对 GenericsInterface 的另一个引用, 针对整数进行操作 // 为其分配一个 lambda 表达式 GenericsInterface<Integer> ref2 = (n) -> n * 100; System.out.println("5 * 100 = " + ref2.func(5)); } }
输出:
lambda 转为大写: LAMBDA 5 * 100 = 500
在上面的例子中,我们创建了一个名为
GenericsInterface
的泛型函数接口。它包含一个名为func()
的泛型方法。这里,在
GenericsMain
类里面:
GenericsInterface<String> ref GenericInterface<Integer> ref2
Lambda 表达式和 Stream API
JDK8 中加入了新的
java.util.stream
包,它允许java开发者对集合进行搜索、过滤、映射、还原等操作。例如,我们有一个数据流(在我们的例子中是一个字符串列表),每个字符串是省市名和地区名的组合。现在,我们可以处理这个数据流,只检索上海的地方。
为此,我们可以通过
Stream API
和Lambda表达式
的组合在流中进行批量操作。示例6: 在 Stream API 中使用 Lambda 操作的演示
import java.util.ArrayList; import java.util.List; public class StreamTest { // 使用 ArrayList 创建集合对象 static List<String> regionList = new ArrayList<String>() {{ add("上海市,青浦区"); add("上海市,闵行区"); add("北京市,朝阳区"); add("北京市,海淀区"); }}; public static void main(String[] args) { System.out.println("来自上海的地区:"); regionList.stream() .filter((p) -> p.startsWith("上海")) .map((p) -> p.split(",")[1]) .sorted() .forEach((p) -> System.out.println(p)); } }
输出:
来自上海的地区: 闵行区 青浦区
在上面的例子中,特别注意如下代码:
regionList.stream() .filter((p) -> p.startsWith("上海")) .map((p) -> p.split(",")[1]) .sorted() .forEach((p) -> System.out.println(p));
在这里,我们使用的是
Stream API
的filter()
、map()
和forEach()
等方法。这些方法可以接受一个 Lambda 表达式作为输入。 - 我们可以根据上面学到的语法定义我们自己的表达式。
- 这使我们能够大幅减少代码行数,正如我们在上面的例子中看到的那样。