Java — Lambda 表达式与函数式接口解析
在 Java 8 引入 Lambda 表达式 和 函数式接口 之后,Java 语言在简洁性与表达力上有了质的提升。本文将逐步讲解相关的核心知识点,包括 函数式接口定义、Lambda 使用、方法引用 以及 常见函数式接口。
一、函数式接口(Functional Interface)
定义:接口中只允许存在一个抽象方法(可以有多个 default
或 static
方法),这样的接口就可以作为 Lambda 表达式 的目标类型。
示例:
@FunctionalInterface
interface MyInterface {int sum(int i, int j);
}
这里的 MyInterface
只有一个抽象方法 sum
,因此是一个函数式接口。
Java 提供了 @FunctionalInterface
注解来保证语义明确,编译器会强制检查该接口是否满足“只有一个抽象方法”的条件。
还可以有 default
方法:
@FunctionalInterface
interface MyInterface1 {int haha();default int hello() {return 2;}
}
即便 hello
是 default
,haha
仍然是唯一抽象方法,所以依然是函数式接口。
二、Lambda 表达式的语法与应用
Lambda 表达式本质上是对 匿名类 的简化,它能让代码更简洁、可读性更强。
示例:
MyInterface myInterface = (i, j) -> i + j;
System.out.println(myInterface.sum(1, 2)); // 输出 3
对应的传统写法需要创建一个匿名类:
MyInterface myInterface = new MyInterface() {@Overridepublic int sum(int i, int j) {return i + j;}
};
Lambda 大幅简化了开发。
三、方法引用(Method Reference)
Lambda 有时只是调用已有方法,可以进一步简化为 方法引用。
示例:
Collections.sort(list, String::compareTo);
等价于:
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
方法引用有四类:
类名::静态方法
对象::实例方法
类名::实例方法
类名::new
(构造器引用)
四、常见函数式接口
java.util.function
包预定义了大量常用函数式接口。
1. Consumer<T>
/ BiConsumer<T, U>
—— 有入参,无返回值
BiConsumer<String, String> consumer = (a, b) -> System.out.println(a + b);
consumer.accept("Hello ", "World");
2. Function<T, R>
/ BiFunction<T, U, R>
—— 有入参,有返回值
Function<String, Integer> function = s -> s.length();
System.out.println(function.apply("Hello World")); // 11BiFunction<String, Integer, Integer> biFunction = (s, i) -> s.substring(i).length();
System.out.println(biFunction.apply("Hello World, Mario", 5));
3. Supplier<T>
—— 无入参,有返回值
Supplier<String> supplier = () -> UUID.randomUUID().toString();
System.out.println(supplier.get());
4. Runnable
—— 无入参,无返回值
Runnable runnable = () -> System.out.println("Hello World");
new Thread(runnable).start();
5. Predicate<T>
—— 断言型接口(返回 boolean
)
Predicate<Integer> even = i -> i % 2 == 0;
System.out.println(even.test(2)); // true
System.out.println(even.negate().test(2)); // false
这里的 negate()
会返回一个新的 Predicate,逻辑是:
(t) -> !even.test(t)
下面用一个例子来展示如何使用 Supplier(供给者)、Predicate(断言)、Function(转换器)、Consumer(消费者) 这四类核心接口,并将它们自然地串联在一起,构建一条“数据处理流水线”。
private static void myMethod(Predicate<String> isNumber,Supplier<String> supplier,Consumer<Integer> consumer,Function<String, Integer> change) {if (isNumber.test(supplier.get())) {consumer.accept(change.apply(supplier.get()));} else {System.out.println("not a number");}
}
流程可以分解为:
- Supplier 负责“提供数据”
- Predicate 验证数据是否合法(是否为数字)
- Function 将字符串转换为整数
Integer
- Consumer 消费结果
这就是一种典型的 函数组合 思路。完整代码如下:
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;public class FunctionDemo {public static void main(String[] args) {// 1. 定义数据提供者函数Supplier<String> supplier = () -> "47";// 2. 断言:验证是否为数字Predicate<String> isNumber = str -> str.matches("-?\\d+(\\.\\d+)?");// 3. Function转换器:把字符串变成数字Function<String, Integer> change = str -> Integer.parseInt(str);// 4. 消费者:打印数字Consumer<Integer> consumer = integer -> {if (integer % 2 == 0) System.out.println("even: " + integer);else System.out.println("odd: " + integer);};// 第一次调用myMethod(isNumber, supplier, consumer, change);// 使用 Lambda 与方法引用直接传参myMethod(str -> str.matches("-?\\d+(\\.\\d+)?"),() -> "777",integer -> {if (integer % 2 == 0) System.out.println("even: " + integer);else System.out.println("odd: " + integer);},Integer::parseInt);}private static void myMethod(Predicate<String> isNumber,Supplier<String> supplier,Consumer<Integer> consumer,Function<String, Integer> change) {// 将上述函数串在一起,判断42的奇偶性if (isNumber.test(supplier.get())) {consumer.accept(change.apply(supplier.get()));} else {System.out.println("not a number");}}
}
程序输出为:
odd: 47
odd: 777
五、测试程序(仅为练习使用)
import java.util.ArrayList;
import java.util.Collections;
import java.util.UUID;
import java.util.function.*;// 函数式接口:接口中只有一个(有且仅有一个)未实现的方法,这个接口就叫函数式接口,只要是函数式接口就可以用Lambda表达式简化
interface MyInterface {int sum(int i, int j);
}// 这个接口也符合有且仅有一个未实现的方法 这个标准,因此也是函数式接口
// 如果以后检查某个接口是否为函数式接口,可以用 @FunctionalInterface 注解
// 如果没有报错,说明是函数式接口
@FunctionalInterface
interface MyInterface1 {int haha();default int hello() {return 2;}
}public class Lambda {public static void main(String[] args) {MyInterface myInterface = (i, j) -> i + j;System.out.println(myInterface.sum(1, 2));MyInterface1 myInterface1 = () -> 1;System.out.println(myInterface1.haha());var list = new ArrayList<String>();list.add("Alice");list.add("Bob");list.add("Charlie");list.add("David");Collections.sort(list, (s1, s2) -> s2.compareTo(s1));System.out.println(list);// 类::方法 引用类中的实例方法(或静态方法);忽略lambda的完整写法Collections.sort(list, String::compareTo);System.out.println(list);// new Thread(() -> System.out.println("Hello World")).start();// 1. 有入参,无出参BiConsumer<String, String> consumer = (a, b) -> System.out.println(a + b);consumer.accept("Hello ", "World");// 2. 有入参,有出参Function<String, Integer> function = s -> s.length();System.out.println(function.apply("Hello World"));BiFunction<String, Integer, Integer> biFunction = (s, i) -> s.substring(i).length();System.out.println(biFunction.apply("Hello World, Mario", 5));// 3. 无入参,无出参Runnable runnable = () -> System.out.println("Hello World");new Thread(runnable).start();// 4. 无入参,有出参Supplier<String> supplier = () -> UUID.randomUUID().toString();System.out.println(supplier.get());// 断言Predicate<Integer> even = i -> i % 2 == 0;System.out.println(even.test(2)); // 正向判断System.out.println(even.negate().test(2)); // 反向判断/**even.negate()even 是 i -> i % 2 == 0negate() 会返回一个新的 Predicate:(t) -> !even.test(t).test(2)调用这个新的 Predicate 的 test(2)它内部会先调用 even.test(2) → 结果是 true再取反 → false*/}
}
程序运行结果为:
3
1
[David, Charlie, Bob, Alice]
[Alice, Bob, Charlie, David]
Hello World
11
13
Hello World
8209a7dd-8a1e-4c2e-bf1b-669fedda6b46
true
false
六、总结
- 函数式接口 是 Lambda 的基础,一个接口只能有一个抽象方法。
- Lambda 表达式 极大简化了匿名类写法,使 Java 更接近函数式编程。
- 方法引用 可以在 Lambda 仅仅调用现有方法时使用,更加简洁。
- Java 8 内置的
Consumer
、Function
、Supplier
、Predicate
等接口满足了大多数常见场景。
得益于 Lambda 与函数式接口,Java 在流式 API(Stream API)、并发编程、事件驱动开发等领域变得更强大、更现代化。