Lambda表达式与函数式接口

Lambda表达式与函数式接口

Lambda 表达式是 Java 8 引入的最重要的语法特性,它让 Java 支持函数式编程风格。本文从基础语法到高级应用进行全面讲解。

Lambda 基础语法

// 传统匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};

// Lambda 表达式
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");

// Predicate:过滤
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());

// Function:映射
List<Integer> lengths = names.stream()
.map(name -> name.length())
.collect(Collectors.toList());

// Consumer:遍历
names.forEach(name -> System.out.println(name));

// Supplier:延迟创建
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();

// Lambda 方式
new Thread(() -> System.out.println("Running")).start();

闭包与变量捕获

Lambda 可以捕获外部变量,但必须是 final 或 effectively final

int factor = 2;

// 正确:factor 未被修改
Function<Integer, Integer> multiplier = x -> x * factor;

// 错误:factor 被修改
factor = 3; // 编译错误

注意:捕获的是值,不是引用。对于对象,引用本身不能变,但对象状态可以变。

复合函数

Function<Integer, Integer> addOne = x -> x + 1;
Function<Integer, Integer> multiplyByTwo = x -> x * 2;

// 先执行 addOne,再执行 multiplyByTwo
Function<Integer, Integer> combined = addOne.andThen(multiplyByTwo);
// 3 -> addOne(3) = 4 -> multiplyByTwo(4) = 8

// 先执行 multiplyByTwo,再执行 addOne
Function<Integer, Integer> composed = addOne.compose(multiplyByTwo);
// 3 -> multiplyByTwo(3) = 6 -> addOne(6) = 7

自定义函数式接口

@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}

// 包装带异常的 Lambda
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 可以内联优化
  • 避免每次创建匿名类实例

最佳实践

  1. 保持简洁:Lambda 体不宜过长,复杂逻辑提取为方法
  2. 使用方法引用:可读性更好
  3. 避免副作用:不要在 Lambda 中修改外部状态
  4. 类型推断:通常不需要显式声明参数类型
  5. 链式操作: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 代码的关键。


   转载规则


《Lambda表达式与函数式接口》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录