携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
引言
在这篇文章,我们将学习 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 表达式作为输入。
- 我们可以根据上面学到的语法定义我们自己的表达式。
- 这使我们能够大幅减少代码行数,正如我们在上面的例子中看到的那样。