Lambda表达式
Lambda 表达式是 Java 8 的重要新特性,它允许我们使用简洁的语法来表示可传递的匿名函数。
lambda 表达式的基本语法是:
(parameters) -> expression
或
(parameters) ->{ statements; }
lambda 表达式的语法结构主要由三个部分组成:
1.参数列表:用于接收 lambda 表达式的输入,类似方法的参数列表。参数列表可以为空,使用 () 表示。
2.箭头标记:用于将参数列表和 lambda 主体分隔开,双冒号 (->)。
3.lambda 主体:用于定义 lambda 表达式的操作逻辑,可以是表达式或代码块。表达式使用大括号 {} 包围。
// 无参数,表达式主体
() -> System.out.println("Hello")
// 单参数,表达式主体
(x) -> x + 1
// 两个参数,表达式主体
(x, y) -> x + y
// 两参数,代码块主体
(x, y) -> {
System.out.println(x);
System.out.println(y);
}
lambda 表达式需要由函数接口来指导。所谓函数接口,是仅包含一个抽象方法的接口。因为 lambda 表达式只能表示一个方法的操作,可以使用@FunctionInterface来限制接口为一个函数接口,但是大多数情况下,我们不需要自行定义函数接口,因为 JDK 已经内置了许多常用的函数接口供我们使用。
函数式接口
常用的函数式接口有:
- Consumer
:消费一个 T 类型的参数,无返回值。包含 void accept(T t) 方法。
示例:
public class ConsumerDemo {
public static void doSomething(Consumer<String> consumer) {
String message = "Hello World!";
consumer.accept(message);
}
public static void main(String[] args) {
doSomething(x -> System.out.println(x));
}
}
// Prints "Hello World!"
- Supplier
:供给 T 类型的结果,无参数。包含 T get() 方法。
示例:
public class SupplierDemo {
public static String getMessage(Supplier<String> supplier) {
return supplier.get();
}
public static void main(String[] args) {
String msg = getMessage(() -> "Hello");
System.out.println(msg);
}
}
// Prints "Hello"
- Predicate
:用于判断 T 类型的参数,返回一个 boolean 结果。包含 boolean test(T t) 方法。
示例:
public class PredicateDemo {
public static boolean doCheck(Predicate<Integer> predicate, int num) {
return predicate.test(num);
}
public static void main(String[] args) {
boolean result = doCheck(x -> x > 10, 15);
System.out.println(result);
}
}
// Prints true
- Function<T, R>:将 T 类型的参数转换为 R 类型的结果。包含 R apply(T t) 方法。Function接口比较特殊,涉及输入输出参数的转换,用来表示有输入有输出的函数。而Supplier、Consumer和Predicate接口主要涉及单一类型的输入或输出。
示例:在这个例子中,我们首先创建一个整数列表,然后使用 lambda 表达式创建一个将整数加上 1 的函数。接下来,我们将这个函数作为参数传递给 processList
方法。在方法的实现中,我们使用 apply
方法将传递的函数应用于列表中的每个整数,并输出结果到控制台上。在这个例子中,结果将是列表中每个整数加上 1 的字符串形式。Function<Integer, String>的意思为Integer
作为输入类型,将 String
作为输出类型
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用 lambda 表达式创建一个将整数加上 1 的函数
Function<Integer, String> addOne = (num) -> Integer.toString(num + 1);
// 将函数作为参数传递给 processList 方法
processList(numbers, addOne);
}
public static void processList(List<Integer> list, Function<Integer, String> func) {
for (Integer i : list) {
String result = func.apply(i);
System.out.println(result);
}
}
}
所以,这四种函数接口涵盖了最基本但却最常用的功能:
消费一个输入:Consumer
产生一个输出:Supplier
将输入转换为输出:Function
用于判断输入的条件:Predicate
lambda 表达式主要用来定义行内执行的方法或接口的匿名实现类。它允许我们简化编码方式,而不必定义匿名类。常见的 Lambda 表达式应用场景有:
- 行内定义 Comparator:
Arrays.asList(1, 3, 2).sort((x, y) -> Integer.compare(x, y));
- 作为接口的匿名实现:
Runnable r = () -> System.out.println("Hello");
- 作为方法的参数:
list.forEach(x -> System.out.println(x));
- 事件监听器的实现:
button.setOnAction(event -> handleActionEvent(event));
当我们使用 lambda 表达式时,实际上是在创建函数接口的匿名实现类。例如:
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
这实际上等同于:
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
return Integer.compare(x, y);
}
};
所以,理解了 lambda 表达式与函数接口之间的关系,我们便可以灵活运用 lambda 来简化我们的代码,并替代传统的匿名内部类。
方法引用
要使用方法引用,需要满足两个条件:
- 方法的参数列表与返回值类型与函数式接口中的抽象方法相兼容。
- 方法引用所指向的方法不能是抽象的,必须是具体的方法。
方法引用有三种使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
我们以Java中四个常用的函数式接口为例,说明方法引用满足条件的情况:
// 情况1:对象::实例方法名
Function<String, Integer> function = "abc"::length;
// 情况2:类::静态方法名
Function<Double, Long> function1 = Double::longValue;
// 情况3:类::实例方法名
Function<String, Boolean> function2 = String::isEmpty;
- Supplier
// 情况3:类::实例方法名
Supplier<LocalDate> supplier = LocalDate::now;
- Consumer
// 情况2:类::静态方法名
Consumer<String> consumer = System.out::println;
- Predicate
// 情况3:类::实例方法名
Predicate<String> predicate = String::isEmpty;
对这四个函数式接口来说:- Function接口,方法引用所指向的方法必须具有一个参数,返回值类型必须相同。可以使用情况1、2和3。
- Supplier接口,方法引用所指向的方法必须无参数,返回值类型必须相同。可以使用情况3。
- Consumer接口,方法引用所指向的方法必须具有相同的参数,返回值类型必须为void。可以使用情况2。
- Predicate接口,方法引用所指向的方法必须具有相同的参数,返回值类型必须为boolean。可以使用情况3。如果方法引用所指向的方法与上述函数式接口的抽象方法在参数列表和返回值类型上完全一致,那么就满足第一个条件,可以使用方法引用。必须指向具体的方法,才满足第二个条件。