Skip to content

函数式接口

想象这样一个场景:你需要传递一段行为——不是数据,不是对象,而是一段可执行的逻辑。

在 Java 8 之前,你得这样写:

java
// 创建一个线程,执行一段代码
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
});
t.start();

匿名内部类写起来繁琐,代码比意图更显眼。

有了函数式接口Lambda 表达式,同样的逻辑可以这样写:

java
Thread t = new Thread(() -> System.out.println("Hello"));
t.start();

简洁到只剩核心逻辑。

什么是函数式接口

函数式接口是只有一个抽象方法的接口。Runnable 是最经典的例子:

java
@FunctionalInterface  // 可选,但建议加上
public interface Runnable {
    void run();  // 只有一个抽象方法
}

@FunctionalInterface 注解不是必须的,但加上后,编译器会帮你检查「这个接口是否真的只有一个抽象方法」。

Lambda 表达式

Lambda 表达式是函数式接口的简洁实现方式。

基本语法

java
// 完整写法
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Lambda 写法
Runnable r2 = () -> System.out.println("Hello");

语法规则:

(参数) -> { 方法体 }
()      ->  { System.out.println("Hello"); }   // 无参数
x       ->  { return x * 2; }                 // 单参数(括号可省略)
(x, y)  ->  { return x + y; }                  // 多参数

省略规则

java
// 参数类型可省略(编译器推断)
Comparator<String> c1 = (String a, String b) -> a.length() - b.length();
Comparator<String> c2 = (a, b) -> a.length() - b.length();  // 类型省略

// 单参数可省略括号
Consumer<String> c3 = (String s) -> System.out.println(s);
Consumer<String> c4 = s -> System.out.println(s);  // 括号省略

// 单行方法体可省略花括号和 return
Function<Integer, Integer> f1 = (Integer x) -> { return x * 2; };
Function<Integer, Integer> f2 = x -> x * 2;  // 简写

内置函数式接口

JDK 8 提供了一组常用的函数式接口,在 java.util.function 包中:

接口抽象方法说明示例
Runnablevoid run()无参数无返回值() -> System.out.println("hi")
Supplier<T>T get()无参数有返回值() -> "hello"
Consumer<T>void accept(T t)消费参数,无返回值s -> System.out.println(s)
Function<T,R>R apply(T t)转换,有参数有返回值s -> s.length()
Predicate<T>boolean test(T t)判断,返回布尔值s -> s.isEmpty()
BiConsumer<T,U>void accept(T t, U u)消费两个参数(a, b) -> System.out.println(a + b)
BiFunction<T,U,R>R apply(T t, U u)处理两个参数(a, b) -> a + b
UnaryOperator<T>T apply(T t)一元操作(参数返回同类)x -> x + 1
BinaryOperator<T>T apply(T t1, T t2)二元操作(a, b) -> a + b

基本类型特化

为了避免装箱拆箱开销,基本类型有专门的版本:

java
// IntFunction<R>:接收 int,返回 R
IntFunction<String> intToString = i -> String.valueOf(i);

// IntConsumer:接收 int,无返回
IntConsumer printInt = i -> System.out.println(i);

// IntSupplier:无参数,返回 int
IntSupplier randomInt = () -> new Random().nextInt();

// IntPredicate:接收 int,返回 boolean
IntPredicate isEven = i -> i % 2 == 0;

// ToIntFunction<T>:接收 T,返回 int
ToIntFunction<String> toLength = s -> s.length();

实战应用

集合排序

java
List<String> names = Arrays.asList("Tom", "Alice", "Bob");

// 匿名内部类
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

// Lambda 表达式
names.sort((s1, s2) -> s1.length() - s2.length());

// 方法引用(更简洁)
names.sort(Comparator.comparingInt(String::length));

集合遍历

java
List<String> list = Arrays.asList("a", "b", "c");

// forEach + Lambda
list.forEach(s -> System.out.println(s));

// 方法引用
list.forEach(System.out::println);

过滤器

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

// 过滤偶数
List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

// 过滤并转换
List<String> names = numbers.stream()
    .filter(n -> n > 3)
    .map(Object::toString)
    .collect(Collectors.toList());

延迟执行

java
// 定义行为,不立即执行
Supplier<User> userSupplier = () -> fetchUserFromDatabase();

// 需要时才执行
if (needUser) {
    User user = userSupplier.get();
}

方法引用

当 Lambda 表达式只调用一个已有方法时,可以进一步简化为方法引用

java
// Lambda
s -> System.out.println(s)
s -> Integer.parseInt(s)
(a, b) -> a.compareTo(b)

// 方法引用
System.out::println
Integer::parseInt
String::compareTo

方法引用的几种形式:

java
// 静态方法引用
ClassName::staticMethod     // Integer::parseInt

// 实例方法引用(特定对象)
instance::instanceMethod    // str::length

// 实例方法引用(任意对象)
ClassName::instanceMethod  // String::toUpperCase

// 构造函数引用
ClassName::new             // User::new

组合使用

函数式接口可以被组合,形成更复杂的行为:

java
// Predicate 组合
Predicate<String> nonNull = s -> s != null;
Predicate<String> nonBlank = s -> !s.isBlank();
Predicate<String> valid = nonNull.and(nonBlank);  // 且
Predicate<String> notValid = nonNull.negate();     // 非

// Function 组合
Function<String, Integer> parse = Integer::parseInt;
Function<Integer, String> toString = Object::toString;
Function<String, String> pipeline = parse.andThen(toString);
pipeline.apply("123");  // "123"

// Comparator 组合
Comparator<String> byLength = Comparator.comparingInt(String::length);
Comparator<String> byAlphabet = Comparator.naturalOrder();
Comparator<String> combined = byLength.thenComparing(byAlphabet);

注意事项

1. 不要过度使用

Lambda 不是万能药。当逻辑复杂时,单独的方法或匿名内部类可能更清晰:

java
// ❌ 过度使用 Lambda
list.stream()
    .filter(s -> {
        if (s == null) return false;
        if (s.length() > 10) return false;
        return s.startsWith("A");
    });

// ✅ 提取为方法
list.stream()
    .filter(this::isValidName);

// 或者用匿名内部类(逻辑复杂时)
list.stream()
    .filter(new Predicate<String>() {
        @Override
        public boolean test(String s) {
            // 复杂的判断逻辑
        }
    });

2. Lambda 中的变量作用域

java
int base = 10;  // effectively final
// base = 20;  // 如果放开这行,Lambda 会编译失败

Function<Integer, Integer> add = x -> x + base;

Lambda 可以访问外部变量,但该变量必须是 effectively final(只赋值一次)。

3. this 的含义

Lambda 中的 this 指向包含它的类,而不是 Lambda 本身:

java
public class MyClass {
    public void doSomething() {
        Runnable r = () -> {
            // 这里的 this 是 MyClass,不是 Runnable
            System.out.println(this);
        };
        r.run();
    }
}

总结

函数式接口 = 一个抽象方法的接口
Lambda    = 实现函数式接口的简洁写法
方法引用  = Lambda 的进一步简化

函数式接口 + Lambda 表达式是 Java 8 最重要的新特性之一。它们让代码变得更简洁,也让「传递行为」变得自然——从这一刻起,Java 真正拥抱了函数式编程的思想。

基于 VitePress 构建