Java Lambda 表达式
在本文中,我们将通过示例学习 Java lambda 表达式、函数式接口和 Stream API。
在本文中,我们将通过示例学习 Java lambda 表达式、函数式接口和 Stream API。
Lambda 表达式是在 Java 8 中引入的, Lambda 表达式能让程序更加简洁。
Lambda 表达式的基础是函数式接口,我们先来了解一下函数式接口。
什么是函数式接口?
如果一个 Java 接口有且只有一个抽象方法,那么它被称为函数式接口。接口中的唯一的抽象方法指明了接口的预期用途。
例如, java.lang
包中的 Runnable
接口就是一个函数式接口,因为它只有一个 run()
方法。
示例 1:在 java 中定义函数式接口
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
// 唯一的抽象接口
double getValue();
}
在上面的例子中,接口 MyInterface
只有一个抽象方法 getValue()
。因此,它是一个函数式接口。
在这里,我们使用了注解 @FunctionalInterface
,用以向 Java 编译器指示该接口是一个函数式接口。 Java 编译器在编译阶段就会检查此接口是否符合函数式接口的规则。
示例 2:在 java 中用匿名类实现函数式接口
public class Main {
public static void main(String[] args) {
new Thread(
// 匿名类
new Runnable() {
@Override
public void run() {
System.out.println("通过匿名类实现了函数式接口。");
}
})
.start();
}
}
输出:
通过匿名类实现了函数式接口。
在这里,我们将匿名类对象传递给 Thread
类的构造方法。这有助于用更少的代码编写程序。
Java 8 进一步扩展了函数式接口的写法。由于函数式接口只有一个方法,Lambda 表达式允许我们直接在表达式中实现函数式接口。
lambda 表达式简介
Lambda 表达式用于实现由函数式接口定义的方法,本质上是一种匿名或未命名的方法。lambda 表达式不会自行执行, lambda 表达式只是定义了一个函数式接口的实例。
如何在 Java 中定义 lambda 表达式?
lambda 表达式的语法如下:
(parameter list) -> lambda body
运算符 ->
称为箭头运算符或 lambda 运算符。语法目前可能有些过于简单,让我们看几个例子,
假设,我们有一个这样的方法:
double getPiValue() {
return 3.1415;
}
我们可以使用 lambda 表达式编写此方法为:
() -> 3.1415
在这里,该方法没有任何参数。因此,->
的左侧的 ()
中的参数列表为空 。->
右侧是 lambda 表达式操作的 lambda 主体。在本例中,返回值是 3.1415
。
Lambda 体的类型
在 Java 中,lambda 主体有两种类型。
-
单一表达式主体
() -> System.out.println("Lambdas are great");
这种类型的 lambda 主体称为表达式主体。
-
代码块组成的主体
() -> { double pi = 3.1415; return pi; };
这种类型的 lambda 主体和普通方法的代码块没有区别,代码块中允许多个语句,允许返回值。
注意:对于代码块体,如果有返回一个值,必须使用 return
语句;而表达式主体不需要 return
关键字直接返回值。
示例 3:Lambda 表达式
让我们编写一个 Java 程序,使用 lambda 表达式返回 Pi
的值。
如前所述,lambda 表达式不会自行执行,lambda 表达式只是定义了一个函数式接口的实例。
所以,我们需要先定义一个函数式接口。
import java.lang.FunctionalInterface;
// 函数式接口
@FunctionalInterface
interface MyInterface {
// 抽象方法
double getPiValue();
}
public class Main {
public static void main(String[] args) {
// 通过 lambda 表达式定义函数式接口实例
MyInterface ref = () -> 3.1415;
System.out.println("Pi = " + ref.getPiValue());
}
}
输出:
Pi = 3.1415
在上面的例子中,
-
我们创建了一个名为
MyInterface
的函数式接口, 它包含一个名为getPiValue()
的抽象方法。 -
在
Main
类中,我们不能像下面一样直接实例化接口:// 报错 MyInterface ref = new myInterface();
-
然后我们为引用分配了一个 lambda 表达式。
ref = () -> 3.1415;
-
最后,我们使用接口的实例引用调用
getPiValue()
方法System.out.println("Pi = " + ref.getPiValue());
带参数的 Lambda 表达式
我们在上面例子中创建的是没有任何参数的 lambda 表达式。接下来我们展示一下带参数的 lambda 表达式。 例如,
(n) -> (n%2) == 0
这里,括号内的变量 n 是传递给 lambda 表达式的参数。lambda 主体接受参数并检查它是偶数还是奇数。
示例 4:使用带参数的 lambda 表达式
@FunctionalInterface
interface MyInterface {
// 反转字符串
String reverse(String n);
}
public class Main {
public static void main(String[] args) {
MyInterface ref =
(str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i);
return result;
};
System.out.println("Lambda 反转后 = " + ref.reverse("Lambda"));
}
}
输出
Lambda 反转后 = adbmaL
带泛型的函数式接口
到目前为止,我们已经使用了只接受一种数据类型值的函数式接口。例如,
@FunctionalInterface
interface MyInterface {
String reverseString(String n);
}
上述函数式接口的方法只接受 String
并返回 String
。但是,我们可以使用带泛型的函数式接口,以便接受任何数据类型。如果您不了解泛型,请访问 Java 泛型。
示例 5:带泛型的函数接口和 Lambda 表达式
@FunctionalInterface
interface GenericInterface<T> {
// 带泛型的方法
T func(T t);
}
public class Main {
public static void main(String[] args) {
// 当类型为字符串时,实现字符串反转
GenericInterface<String> reverse =
(str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i);
return result;
};
System.out.println("Lambda 反转后 = " + reverse.func("Lambda"));
// 当类型为整数时,求从 1 到给定参数的连续乘积:阶乘
GenericInterface<Integer> factorial =
(n) -> {
int result = 1;
for (int i = 1; i <= n; i++) result = i * result;
return result;
};
System.out.println("5 的阶乘 = " + factorial.func(5));
}
}
输出
Lambda 反转后 = adbmaL
5 的阶乘 = 120
在上面的例子中,我们创建了一个名为 GenericInterface
的带泛型的函数式接口, 它包含一个名为 func()
的带泛型的方法。
在这里,在 Main 类中,
GenericInterface<String> reverse
- 创建接口的实例,实现对String
数据类型进行操作。GenericInterface<Integer> factorial
- 创建接口的实例,实现对Integer
数据类型进行操作。
Lambda 表达式和流 API
Java 8 开始,在 java.util.stream
包中引入了 Stream API,它允许开发人员对集合和数组执行诸如搜索、过滤、映射、合并、排序之类的操作。
在下面的例子中,我们有一个 List<String>
列表,包含了不同国家的地点信息,我们要检索出所有 Nepal
的地点。
我们可以通过 Stream API 和 Lambda 表达式的组合用简洁的代码实现需求。
示例 6:Stream API 结合 lambda 表达式
import java.util.ArrayList;
import java.util.List;
public class Main {
// 定义地点列表
static List<String> places = new ArrayList<>();
// 初始化地点数据
public static List<String> getPlaces() {
places.add("Nepal, Pokhara");
places.add("Nepal, Kathmandu");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}
public static void main(String[] args) {
List<String> myPlaces = getPlaces();
System.out.println("Places from Nepal:");
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal")) // 根据 Nepal 过滤数据
.map((p) -> p.toUpperCase()) // 转为大写
.sorted() // 排序
.forEach((p) -> System.out.println(p)); // 输出
}
}
输出
Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA
注意上面例子中的语句:
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
在这里,我们使用 Stream API 了的 filter()
、 map()
和 forEach()
方法,这些方法可以将 lambda 表达式作为输入。
正如我们在上面的示例中看到的那样, lambda 表达式能够大幅减少代码行数。