Lambda表达式与函数式接口
Lambda 表达式是 Java 8 引入的最重要的语法特性,它让 Java 支持函数式编程风格。本文从基础语法到高级应用进行全面讲解。
Lambda 基础语法
Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello"); } };
Runnable runnable = () -> System.out.println("Hello");
|
语法格式
() -> expression
x -> x * 2
(x, y) -> x + y
(x, y) -> { int sum = x + y; return sum; }
|
函数式接口
函数式接口是只有一个抽象方法的接口:
@FunctionalInterface public interface Predicate<T> { boolean test(T t); }
|
核心函数式接口
| 接口 |
方法签名 |
用途 |
| Predicate |
boolean test(T t) |
断言/过滤 |
| Function<T,R> |
R apply(T t) |
转换 |
| Consumer |
void accept(T t) |
消费 |
| Supplier |
T get() |
供给 |
| UnaryOperator |
T apply(T t) |
一元操作 |
| BinaryOperator |
T apply(T t1, T t2) |
二元操作 |
使用示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> longNames = names.stream() .filter(name -> name.length() > 3) .collect(Collectors.toList());
List<Integer> lengths = names.stream() .map(name -> name.length()) .collect(Collectors.toList());
names.forEach(name -> System.out.println(name));
Supplier<List<String>> listSupplier = () -> new ArrayList<>();
|
方法引用
当 Lambda 体只是调用一个已有方法时,可以使用方法引用:
Function<Integer, String> toString = String::valueOf;
String prefix = "Hello, "; Function<String, String> addPrefix = prefix::concat;
Function<String, Integer> getLength = String::length;
Supplier<List<String>> createList = ArrayList::new; Function<Integer, List<String>> createListWithSize = ArrayList::new;
|
常用场景
1. 集合操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream() .filter(n -> n % 2 == 0) .map(n -> n * n) .reduce(0, Integer::sum);
|
2. 排序
List<Person> people = getPeople();
people.sort(Comparator.comparing(Person::getAge));
people.sort(Comparator.comparing(Person::getAge).reversed());
people.sort(Comparator.comparing(Person::getAge) .thenComparing(Person::getName));
|
3. 线程创建
new Thread(new Runnable() { @Override public void run() { System.out.println("Running"); } }).start();
new Thread(() -> System.out.println("Running")).start();
|
闭包与变量捕获
Lambda 可以捕获外部变量,但必须是 final 或 effectively final:
int factor = 2;
Function<Integer, Integer> multiplier = x -> x * factor;
factor = 3;
|
注意:捕获的是值,不是引用。对于对象,引用本身不能变,但对象状态可以变。
复合函数
Function<Integer, Integer> addOne = x -> x + 1; Function<Integer, Integer> multiplyByTwo = x -> x * 2;
Function<Integer, Integer> combined = addOne.andThen(multiplyByTwo);
Function<Integer, Integer> composed = addOne.compose(multiplyByTwo);
|
自定义函数式接口
@FunctionalInterface public interface ThrowingConsumer<T, E extends Exception> { void accept(T t) throws E; }
public static <T, E extends Exception> Consumer<T> wrap( ThrowingConsumer<T, E> throwingConsumer) { return t -> { try { throwingConsumer.accept(t); } catch (Exception e) { throw new RuntimeException(e); } }; }
list.forEach(wrap(item -> { Files.write(path, item.getBytes()); }));
|
性能考量
Lambda 并非语法糖,编译后生成 invokedynamic 字节码:
// 编译后使用 invokedynamic invokedynamic #0:accept:()Ljava/util/function/Consumer;
|
优势:
- 运行时动态绑定,JVM 可以内联优化
- 避免每次创建匿名类实例
最佳实践
- 保持简洁:Lambda 体不宜过长,复杂逻辑提取为方法
- 使用方法引用:可读性更好
- 避免副作用:不要在 Lambda 中修改外部状态
- 类型推断:通常不需要显式声明参数类型
- 链式操作:Stream API 结合 Lambda 使用
list.stream().forEach(item -> { if (item.isActive()) { item.process(); log.info("Processed: " + item.getId()); } });
list.stream() .filter(Item::isActive) .peek(item -> log.info("Processing: " + item.getId())) .forEach(Item::process);
|
总结
Lambda 表达式让 Java 代码更加简洁和函数式。配合 Stream API,可以大幅减少样板代码,提高可读性。掌握函数式接口和方法引用,是写出现代化 Java 代码的关键。