CodeGym /课程 /JAVA 25 SELF /函数式接口:Predicate, Consumer, Supplier, Function

函数式接口:Predicate, Consumer, Supplier, Function

JAVA 25 SELF
第 49 级 , 课程 0
可用

1. 深入理解函数式接口

函数式接口指的是仅包含恰好一个抽象(即未实现的)方法的接口。正因为如此,Java 才能理解:“哦,这里可以传入一个 lambda!”或者方法引用。

为避免出错,这类接口通常会加上注解 @FunctionalInterface。它并非强制,但如果你加上它却又不小心写了第二个抽象方法,编译器会立刻报错。

示例:

@FunctionalInterface
interface MyAction {
    void run();
}
MyAction action = () -> System.out.println("来自 Lambda 的问候!");
action.run(); // 输出:来自 Lambda 的问候!

为什么需要它?

  • 可以用 lambda 表达式或方法引用替代匿名类(更少样板代码!)。
  • 让编译器明确该接口用于函数式编程。

有趣的事实: Java 标准库已经提供了数十个这样的接口——无需重复造轮子!

2. 标准函数式接口概览

java.util.function 包中包含数十个函数式接口。我们来看最常用的四个(它们在各类 Java 接口中“出场率”最高)。

接口 输入 返回 常见用途
Predicate<T>
T
boolean
条件判断(过滤)
Consumer<T>
T
void
对对象执行操作
Supplier<T>
T
获取/生成对象
Function<T, R>
T
R
将 T 转换为 R

Predicate<T>

描述:接收类型为 T 的对象并返回 truefalse。典型示例:列表过滤。核心方法为 test

Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Java"));       // false
System.out.println(isLong.test("Functional")); // true

Consumer<T>

描述:接收类型为 T 的对象并对其执行操作,不返回任何值。核心方法为 accept

Consumer<String> printer = s -> System.out.println("打印:" + s);
printer.accept("Hello, world!"); // 打印:Hello, world!

Supplier<T>

描述:不接收任何参数,返回类型为 T 的对象。可以看作“值的生成器”。核心方法为 get

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get()); // 例如,0.1234567

Function<T, R>

描述:接收类型为 T 的对象并返回类型为 R 的对象。典型示例:数据转换。核心方法为 apply

Function<String, Integer> stringToLength = s -> s.length();
System.out.println(stringToLength.apply("Java")); // 4

简述:UnaryOperator, BinaryOperator, BiFunction

  • UnaryOperator<T> —— 等同于 Function<T, T>:输入与返回类型相同。
  • BinaryOperator<T> —— 等同于 BiFunction<T, T, T>:接收两个 T,返回一个 T。
  • BiFunction<T, U, R> —— 接收两种不同类型,返回第三种类型。
UnaryOperator<Integer> square = x -> x * x;
BinaryOperator<Integer> sum = (a, b) -> a + b;
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);

3. 使用示例

看看这些接口在实际任务中的用法,尤其是在集合与 Stream API 中。

传给集合与 Stream API 的方法

示例 1:Predicate 与过滤

List<String> words = List.of("java", "stream", "lambda", "code");
List<String> longWords = words.stream()
    .filter(word -> word.length() > 4) // Predicate<String>
    .toList();
System.out.println(longWords); // [stream, lambda]

示例 2:Consumer 与 forEach

words.forEach(word -> System.out.println("单词:" + word)); // Consumer<String>

示例 3:Function 与 map

List<Integer> lengths = words.stream()
    .map(word -> word.length()) // Function<String, Integer>
    .toList();
System.out.println(lengths); // [4, 6, 6, 4]

示例 4:Supplier 与值生成

Supplier<String> greetingSupplier = () -> "你好,Java!";
System.out.println(greetingSupplier.get()); // 你好,Java!

与匿名类的对比

过去我们得这样写:

Predicate<String> isShort = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 5;
    }
};

有了 lambda,就简洁多了:

Predicate<String> isShort = s -> s.length() < 5;

4. 实践:为每个接口编写 lambda 表达式

我们实现一个小应用——用户列表。每个用户由类 User 表示:

public class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

创建用户列表:

List<User> users = List.of(
    new User("Anna", 23),
    new User("Boris", 17),
    new User("Vika", 31),
    new User("Gosha", 15)
);

Predicate:筛选成年人

Predicate<User> isAdult = user -> user.getAge() >= 18;
List<User> adults = users.stream()
    .filter(isAdult)
    .toList();

System.out.println("成年人: " + adults); // 成年人: [Anna (23), Vika (31)]

Consumer:打印用户

Consumer<User> printUser = user -> System.out.println("用户:" + user);
adults.forEach(printUser);

Supplier:生成用户

Supplier<User> randomUserSupplier = () -> {
    String[] names = {"Dima", "Katya", "Lyosha"};
    int randomAge = 10 + (int)(Math.random() * 30);
    String randomName = names[(int)(Math.random() * names.length)];
    return new User(randomName, randomAge);
};

User randomUser = randomUserSupplier.get();
System.out.println("随机用户:" + randomUser);

Function:获取用户名

Function<User, String> getName = user -> user.getName();
List<String> names = users.stream()
    .map(getName)
    .toList();

System.out.println("姓名: " + names); // 姓名: [Anna, Boris, Vika, Gosha]

5. 实用细节

在 Stream API 中的使用:filter, map, forEach 等

把这些组合起来,写一条变换管道:

users.stream()
     .filter(user -> user.getAge() >= 18)         // Predicate<User>
     .map(user -> user.getName().toUpperCase())    // Function<User, String>
     .forEach(name -> System.out.println("成年人:" + name)); // Consumer<String>

结果:

成年人: ANNA
成年人: VIKA

速查表:何时用哪个接口

使用场景 所需接口 使用示例
filter (Stream)
Predicate<T>
filter(u -> u.getAge() > 18)
map (Stream)
Function<T, R>
map(u -> u.getName())
forEach (Stream, List)
Consumer<T>
forEach(u -> System.out.println(u))
generate (Stream)
Supplier<T>
Stream.generate(() -> ...)

为什么要了解函数式接口?

  • 它们是 Java 中所有 lambda 表达式的基础。
  • 帮助你编写通用、可复用且简洁的代码。
  • 简化对集合、流与异步任务的处理。

何时使用哪个接口?

  • Predicate —— 需要判断条件时(过滤、查找)。
  • Consumer —— 需要对对象执行某个操作时(打印、写入、发送)。
  • Supplier —— 需要获取或生成对象时(工厂、生成器)。
  • Function —— 需要把一种类型转换为另一种类型时。

6. 使用函数式接口的常见错误

错误 1:接口选择不当。 新手有时会把 PredicateFunction 混淆——例如试图从 Function 返回 boolean,而不是使用 Predicate。记住:Predicate 始终返回 booleanFunction 返回其它任意类型。

错误 2:不使用标准接口。 常见做法是自定义类似“Checker”的接口,并提供 boolean check(T t) 方法,而不是使用 Predicate。优先使用标准接口——它们得到广泛支持,也让代码对他人更易读。

错误 3:lambda 过于复杂。 如果一个 lambda 写成了 10 行的小短文,就该把它提取为单独的方法或类。lambda 追求的是简洁与可读性。

错误 4:遗忘注解 @FunctionalInterface 如果你自定义函数式接口——别忘了这条注解。它能防止意外错误(比如多写了第二个抽象方法)。

错误 5:在 lambda 内部修改可变状态。 如果 lambda 修改了外部变量或集合,尤其在并发场景下,可能引发意料之外的 bug。应尽量避免副作用。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION