一文带你精通泛型PECS原则与四大核心函数式接口

14 阅读18分钟

1. 泛型通配符

泛型允许在定义类、接口和方法时使用类型参数,使代码能够处理多种类型而无需重写。但有时我们并不关心具体的类型参数,只想表达某种类型范围,这时就需要使用通配符 ?

例如,List<?> 表示元素类型未知的列表。但单纯的无界通配符无法表达“某种类型的子类”或“某种类型的父类”,因此引入了有界通配符? extends T? super T

1.1. ? extends T(上界通配符)

含义? extends T 表示类型参数必须是 TT 的子类(包括 T 本身)。例如,List<? extends Number> 可以接受 List<Number>List<Integer>List<Double> 等,因为 IntegerDouble 都是 Number 的子类。

作用

  • 只读安全,当使用 ? extends T 通配符时,你只能从结构中读取元素,而不能向其中写入(除了 null)。这是因为实际类型可能是 T 的任何子类,编译器无法确保你写入的元素类型与实际的未知子类型兼容;
List<? extends Number> list = new ArrayList<Integer>();
Number n = list.get(0);   // 可以读取,返回 Number 类型
list.add(123);            // 编译错误!不能添加 Integer,因为实际类型可能不是 Integer
  • 协变? extends T 使得泛型类型在子类型关系上具有协变性。即,如果 IntegerNumber 的子类,那么 List<Integer> 可以被视为 List<? extends Number> 的子类型。这弥补了 Java 泛型默认是不可变的限制。"Java 泛型默认是不可变的"(Invariance)指的是:即使类型参数之间存在继承关系,泛型类型之间也不存在继承关系。例如,具体来说,如果 StringObject 的子类,但 List<String>并不是List<Object> 的子类。这两个类型被视为完全无关的类型,不能互相赋值;

典型应用场景生产者,当你需要从一个数据结构中读取数据,并将这些数据作为 T 类型处理时,使用 ? extends T。因为你只知道读取的对象是 T 的子类,所以可以安全地以 T 类型处理。

1.2. ? super T(下界通配符)

含义? super T 表示类型参数必须是 TT 的父类(包括 T 本身)。例如,List<? super Integer> 可以接受 List<Integer>List<Number>List<Object> 等,因为 Integer 的父类包括 NumberObject

作用:

  • 只写安全:当你使用 ? super T 通配符时,你可以向结构中写入T 类型或其子类型的元素,但读取时只能得到 Object 类型(因为无法确定具体的父类型)。写入是安全的,因为实际类型是 T 的某个父类,T 或其子类可以向上转型赋值给该父类。
List<? super Integer> list = new ArrayList<Number>();
list.add(123);                // 可以添加 Integer
list.add(456);                // 可以添加 Integer
Object obj = list.get(0);      // 读取只能作为 Object,不能作为 Number(可能实际是 List<Object>)
  • 逆变? super T 使得泛型类型在子类型关系上具有逆变性。即,如果 NumberInteger 的父类,那么 List<Number> 可以被视为 List<? super Integer> 的子类型。

典型应用场景:当你需要向一个数据结构中写入数据,并且这些数据是 T 类型(或其子类)时,使用 ? super T。因为你只需要确保结构能接收 T 类型的对象,而不关心具体是哪个父类。

1.3. PECS 原则

PECS 是 "Producer Extends, Consumer Super" 的缩写,由 Joshua Bloch 在《Effective Java》中提出。它指导如何选择正确的通配符:

  • 如果参数化类型表示一个生产者(producer),就应该使用 ? extends T,生产者从数据结构中读取元素供我们使用。例如,从集合中取出元素并处理。
  • 如果参数化类型表示一个消费者(consumer),就应该使用 ? super T 消费者将元素放入数据结构中。例如,向集合中添加元素。
  • 如果既生产又消费,则应该使用精确的类型参数,不使用通配符。

示例说明

  1. 生产者场景:从集合中复制元素:
  • 假设有一个方法,需要将源集合中的所有元素复制到目标集合中。源集合是生产者(提供元素),目标集合是消费者(接收元素)。
  • src 使用 ? extends T,表示它可以提供 T 类型的元素(或子类),可以安全地读取并作为 T 使用。
  • dest 使用 ? super T,表示它可以接收 T 类型的元素(或其子类),写入 T 类型是安全的。
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
    for (T item : src) {
        dest.add(item);
    }
}

List<Integer> ints = Arrays.asList(1, 2, 3);
List<Number> nums = new ArrayList<>();
copy(ints, nums);   // src 是 List<Integer>(? extends Number),dest 是 List<Number>(? super Number)
  1. 生产者场景:从集合中取数据
  • Collections.max 方法用于返回集合的最大元素,它只从集合中读取数据,因此使用 ? extends 。Tcoll 是生产者,我们从中读取元素并比较。
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
    // 实现
}
  1. 消费者场景:向集合中添加元素
  • Collections.addAll 方法将多个元素添加到集合中,集合是消费者,因此使用 ? super T
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
    for (T element : elements)
        c.add(element);
    return true;
}

注意:

  • 不能同时使用 extends 和 super:一个通配符只能有一个上界或下界。
  • 写入 ? extends T 的不可能性:除了 null,不能向 ? extends T 集合中写入任何值,因为无法保证写入的类型与实际的未知子类型匹配。
  • 读取 ? super T 的局限性:从 ? super T 集合中读取的元素只能作为 Object 处理,因为实际类型可能是任何父类,无法确定更具体的类型。
  • 无界通配符 ? :当既不需要读(作为具体类型),也不需要写(除了 null)时,可以使用无界通配符,它等价于 ? extends Object。例如,List<?> 表示未知类型的列表,只能读取为 Object,不能写入(除 null)。

2. Consumer:消费数据(T → void)

2.1. Consumer介绍

Consumer<T> 是一个函数式接口,它代表了一个接受一个输入参数且不返回结果的操作。它的抽象方法是:

void accept(T t);

这里的“消费”可以理解为:你对某个数据做了一些事情(比如打印、修改、保存),但做完之后不需要给调用者反馈(void)。

Consumer 的核心特点:

  • 输入有,输出无:接收一个参数,处理完后没有返回值。
  • 副作用: 操作通常伴随着状态改变或外部输出(如打印日志、修改对象字段、写入数据库等)
  • 可组合: 通过 andThen 可以串联多个 Consumer,让数据依次经过每个消费逻辑。
  • 与 Stream、Optional 完美结合: Stream.forEachOptional.ifPresent 等方法的参数都是 Consumer。

2.2. Consumer源码解读

accept — 核心消费动作: 每个 Consumer 实例都必须实现 accept 方法,定义自己“如何消费”输入。

andThen — 链式组合: andThen 允许将两个 Consumer 组合成一个新的 Consumer,先执行当前 Consumer,再执行 after Consumer

Consumer<String> first = s -> System.out.print("第一步:" + s);
Consumer<String> second = s -> System.out.println(" -> 第二步:" + s);
Consumer<String> composed = first.andThen(second);
composed.accept("数据");   // 输出:第一步:数据 -> 第二步:数据
package java.util.function;

import java.util.Objects;

/**
 * 表示接受一个输入参数且不返回结果的操作。与大多数其他函数式接口不同,Consumer预期通过副作用来运行。
 */
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);


    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2.3. 为什么要用 Consumer?

  1. 作为参数传递,实现策略模式

Consumer 可以作为方法参数,将“对数据的操作”抽象出来,让方法调用方决定具体怎么消费。

public static void processStudent(Student student, Consumer<Student> consumer) {
    // 公共逻辑(如校验、事务)
    consumer.accept(student);
    // 公共收尾
}

// 调用时自由指定操作
processStudent(student, s -> System.out.println(s.getName()));
processStudent(student, s -> db.save(s));
  1. 链式组合,构建处理流水线

利用 andThen,可以把多个 Consumer 串成一条流水线,数据依次经过每个环节。这在中间件、拦截器设计中非常实用。

  1. 与 Stream、Optional 集成
  • Stream.forEach(Consumer) 对每个元素执行消费。
  • Optional.ifPresent(Consumer) 当值存在时执行消费。
  • Stream.peek(Consumer) 可以在流中间查看元素,不影响流本身(常用于调试)。
Stream.of("a", "b", "c")
    .peek(System.out::println)          // Consumer,仅用于查看
    .map(String::toUpperCase)
    .forEach(System.out::println);       // 最终消费
  1. 订单处理流水线

假设我们有一个订单处理流程,需要依次执行:记录日志 → 保存数据库 → 发送消息通知。

// 定义三个消费逻辑
Consumer<Order> logOrder = order -> System.out.println("处理订单:" + order.getId());
Consumer<Order> saveOrder = order -> db.save(order);
Consumer<Order> sendMQ = order -> mq.sendOrderMessage(order);

// 组合成完整流程
Consumer<Order> processOrder = logOrder.andThen(saveOrder).andThen(sendMQ);

// 执行
Order order = new Order(1001, 299.00);
processOrder.accept(order);
  1. 其他案例应用
package function_interface_01;

import java.util.*;
import java.util.function.Consumer;

public class ConsumerTest {
    public static void main(String[] args) {
        // 打印输出
        Consumer<String> print = System.out::println;
        print.accept("hello");  // hello

        // 保存数据
        Consumer<Student> save = student -> db.insert(student);
        save.accept(new Student("lushimeng", 18));

        // 链式消费
        Consumer<Order> log = order -> logger.info("处理订单: {}", order.getId());
        Consumer<Order> save = order -> db.save(order);
        Consumer<Order> send = order -> mq.sendOrderMessage(order);

        // 一步到位:记日志 → 存库 → 发消息
        log.andThen(save).andThen(send).accept(order);

        // forEach() 接收的就是 Consumer,用来对每个元素执行操作:
        List<Student> studentList = new ArrayList<>();
        Student stu1 = new Student("lu", 18);
        Student stu2 = new Student("yang", 18);
        Collections.addAll(studentList, stu1, stu2);
        studentList.forEach(s -> System.out.println(s.getName()));  // 打印逻辑

        studentList.forEach(s -> {
            s.setName(s.getName().toUpperCase());  // name转化为大写
            System.out.println(s.getName());
        });

        // Map的forEach使用的BiConsumer
        Map<String, Integer> scoreMap = new HashMap<>();
        scoreMap.put("lu", 99);
        scoreMap.put("yang", 99);
        scoreMap.forEach((name, score) -> {
            System.out.println(name + ": " + score);
        });
    }
}

2.4. Consumer变体

Java 8 还提供了一些特化的 Consumer,用于处理基本类型,避免自动装箱/拆箱带来的性能开销。

接口名抽象方法说明
BiConsumer<T, U>void accept(T t,U u)接收两个参数,比如 Map 的 forEach
IntConsumervoid accept(int value)接收一个 int 值
LongConsumervoid accept(long value)接收一个 long 值

3. Supplier:生产数据(void → T)

3.1. Supplier介绍

在 Java 8 的函数式编程中,Supplier 同样是一个基础且强大的接口。如果说 Consumer 是一个只吃不吐的“消费者”,那么 Supplier 就是一个只吐不吃的“供应商”——它专门用来提供数据,不接受任何参数,只返回一个结果。

Supplier<T> 是一个函数式接口,它代表了一个结果提供者。它的抽象方法是:

T get();

调用 get() 方法就能获得一个类型为 T 的对象。它就像一个无参的工厂方法,每次调用都会“生产”出一个新的实例或值。

3.2. Supplier源码解读

源码中只有一个 get() 方法。

@FunctionalInterface
public interface Supplier<T> {

    /**
     * 获取一个结果。
     * @return 结果
     */
    T get();
}

3.3. 为什么要用Supplier

很多可能会说:我直接 new 一个对象或者调用一个方法不就好了,为什么要多此一举用 Supplier?
下面几个场景能让你充分体会到 Supplier 的妙处。

  1. 延迟执行(Lazy Evaluation)

Supplier 的核心价值之一就是延迟执行。get() 方法只有在被调用时才会真正执行里面的逻辑。这在需要避免不必要的计算时非常有用。

典型例子Optional.orElseGet()orElse() 的区别

// orElse:无论 Optional 是否为空,都会执行 createDefault()
User user = optional.orElse(createDefault());

// orElseGet:只有当 Optional 为空时,才会执行 createDefault()
User user = optional.orElseGet(() -> createDefault());

如果 createDefault() 是一个耗时的数据库查询或网络请求,orElseGet 就能避免无谓的开销。

  1. 解耦对象的创建

Supplier 将“如何创建对象”的细节封装起来,传递给调用方,实现策略模式。

public class StudentFactory {
    private final Supplier<Student> supplier;

    public StudentFactory(Supplier<Student> supplier) {
        this.supplier = supplier;
    }

    public Student createStudent() {
        return supplier.get();
    }
}

// 使用不同的创建策略
StudentFactory factory1 = new StudentFactory(() -> new Student("默认", 18));
StudentFactory factory2 = new StudentFactory(Student::new); // 构造器引用
  1. 作为工厂,生成一系列对象

Supplier 可以用来生成无限的数据流,配合 Stream.generate() 非常方便。

Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);
  1. 与 Optional 结合,实现优雅的空值处理

除了 orElseGet,还可以在需要时才从 Supplier 获取默认值,避免提前创建对象。

public String getUserName(Optional<User> optional) {
    return optional.map(User::getName)
                   .orElseGet(() -> fetchDefaultNameFromDB());
}
  1. 其他案例demo
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SupplierTest {
    public static void main(String[] args) {

        // 使用 Lambda 表达式创建 Supplier
        Supplier<String> stringSupplier = () -> "Hello World!";
        System.out.println(stringSupplier.get());

        // 使用方法引用试下Supplier
        Supplier<Double> randomSupplier = Math::random;
        System.out.println(randomSupplier.get());

        // 延迟创建对象
        Supplier<Student> stuFactory = () -> new Student("张三", 18);
        Student s2 = stuFactory.get();

        // 构造器引用
        Supplier<ArrayList<String>> listFactory = () -> new ArrayList<>();
        ArrayList<String> arrayList = listFactory.get();

        // Supplier 最常用的地方是 orElseGet():
        // orElse:立即求值,无论如何都会调用 createDefault()
        user = optional.orElse(createDefault());

        // orElseGet:延迟求值,只有 user 为 null 时才调用
        user = optional.orElseGet(() -> createDefault());

        // 用 Supplier 生成元素流:
        Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);

        // 生成 UUID 列表
        List<String> uuids = Stream.generate(UUID::randomUUID)
                .limit(10)
                .map(UUID::toString)
                .collect(Collectors.toList());
        System.out.println(uuids);


    }

    // 实际例子
    public String getUserName(Optional<Student> optional) {
         // 只有 user 为 null 时,才执行这个耗时的数据库查询
        return optional
                .map(Student::getName)
                .orElseGet(() -> queryDefaultNameFromDB());
    }
}

3.4. Supplier变体

Java 8 也为基本类型提供了特化的 Supplier 接口,避免自动装箱/拆箱带来的性能开销。

接口名抽象方法说明
BooleanSupplierboolean getAsBoolean()返回 boolean 类型的结果
BooleanSupplierboolean getAsBoolean()返回 boolean 类型的结果
IntSupplierint getAsInt()返回 int 类型的结果
package java.util.function;

/**
 * 表示结果提供者。
 */
@FunctionalInterface
public interface Supplier<T> {

    T get();
}

4. Predicate:条件判断(T → boolean)

4.1. Predicate介绍

Predicate<T> 是一个函数式接口,它代表了一个谓词(布尔值函数) ,即对给定参数进行判断,返回 truefalse。它的抽象方法是:

boolean test(T t);

你可以把它理解为一个条件表达式,专门用来做过滤匹配

4.2. Predicate源码解读

除了核心的 test 方法,还提供了三个默认方法:andornegate,用于组合多个 Predicate,以及一个静态方法 isEqual,用于创建相等性判断的 Predicate。

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

4.3. 为什么使用Predicate

  1. 将条件逻辑参数化

传统编程中,我们经常需要根据不同的条件执行不同的操作。如果把这些条件硬编码在方法内部,代码会变得僵硬。使用 Predicate 可以将条件作为参数传递,实现策略模式,让方法更加通用。

// 传统方式:写多个方法
public List<Student> filterAdults(List<Student> list) { ... }
public List<Student> filterHighGpa(List<Student> list) { ... }

// Predicate 方式:一个方法,条件由调用方决定
public List<Student> filter(List<Student> list, Predicate<Student> predicate) {
    List<Student> result = new ArrayList<>();
    for (Student s : list) {
        if (predicate.test(s)) {
            result.add(s);
        }
    }
    return result;
}
  1. 组合逻辑,避免重复代码

通过 andornegate 方法,我们可以将多个简单的 Predicate 组合成复杂的条件,而无需编写大量 if-else 嵌套。

  1. 与 Stream API 无缝集成

Stream.filter(Predicate) 是 Predicate 最经典的应用场景。配合 Lambda 表达式,可以写出极其简洁的数据处理代码。

List<Student> students = ...; 

// 筛选成年人
List<Student> adults = students.stream()
        .filter(s -> s.getAge() >= 18)
        .collect(Collectors.toList());

// 多重筛选(相当于 and)
List<Student> validStudents = students.stream()
        .filter(s -> s.getAge() >= 18)
        .filter(s -> s.getGpa() >= 3.5)
        .filter(s -> s.getAttendance() >= 90)
        .collect(Collectors.toList());

// 或者
Predicate<Student> isAdult = s -> s.getAge() >= 18;
Predicate<Student> hasHighGpa = s -> s.getGpa() >= 3.5;
Predicate<Student> hasGoodAttendance = s -> s.getAttendance() >= 90;

List<Student> result = students.stream()
        .filter(isAdult.and(hasHighGpa).and(hasGoodAttendance))
        .collect(Collectors.toList());
  1. 其他代码案例
// 基本用法:test 方法
// 判断字符串非空
Predicate<String> isNotEmpty = s -> s != null && !s.isEmpty();
System.out.println(isNotEmpty.test(""));      // false
System.out.println(isNotEmpty.test("hello")); // true

// 判断整数为正数
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isPositive.test(5));   // true
System.out.println(isPositive.test(-3));  // false

// 组合 Predicate:and、or、negate
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;

// 正偶数:isPositive && isEven
Predicate<Integer> isPositiveEven = isPositive.and(isEven);
System.out.println(isPositiveEven.test(4));   // true
System.out.println(isPositiveEven.test(-2));  // false
System.out.println(isPositiveEven.test(3));   // false

// 正数或偶数:isPositive || isEven
Predicate<Integer> isPositiveOrEven = isPositive.or(isEven);
System.out.println(isPositiveOrEven.test(4));   // true
System.out.println(isPositiveOrEven.test(-2));  // true
System.out.println(isPositiveOrEven.test(-3));  // false

// 非偶数 = 奇数:!isEven
Predicate<Integer> isOdd = isEven.negate();
System.out.println(isOdd.test(3));  // true
System.out.println(isOdd.test(4));  // false

// 与 Stream.filter 结合
List<Student> students = ...; // 假设已有学生列表

// 筛选成年人
List<Student> adults = students.stream()
        .filter(s -> s.getAge() >= 18)
        .collect(Collectors.toList());

// 多重筛选(相当于 and)
List<Student> validStudents = students.stream()
        .filter(s -> s.getAge() >= 18)
        .filter(s -> s.getGpa() >= 3.5)
        .filter(s -> s.getAttendance() >= 90)
        .collect(Collectors.toList());

//  静态方法 isEqual: isEqual 用于创建一个判断是否与给定对象相等的 Predicate
Predicate<String> isHello = Predicate.isEqual("Hello");
System.out.println(isHello.test("Hello"));   // true
System.out.println(isHello.test("World"));   // false

// 将 Predicate 作为方法参数
public <T> List<T> filterList(List<T> list, Predicate<T> predicate) {
    return list.stream().filter(predicate).collect(Collectors.toList());
}

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = filterList(numbers, n -> n % 2 == 0);
System.out.println(evenNumbers);  // [2, 4]

4.4. Predicate变体

Java 8 也为基本类型提供了特化的 Predicate 接口,避免自动装箱/拆箱带来的性能开销。

接口名抽象方法说明
IntPredicateboolean test(int value)判断 int 值
LongPredicateboolean test(long value)判断 long 值
DoublePredicateboolean test(double value)判断 double 值
BiPredicateboolean test(T t, U u)接收两个参数,返回 boolean 值
IntPredicate isPositive = n -> n > 0;
System.out.println(isPositive.test(5));   // true

LongPredicate isEven = n -> n % 2 == 0;
System.out.println(isEven.test(10L));     // true

DoublePredicate isGreaterThanPi = d -> d > 3.14;
System.out.println(isGreaterThanPi.test(3.5));  // true

// 判断两个字符串是否相等(忽略大小写)、判断两个对象是否满足某种关系。
BiPredicate<String, String> equalsIgnoreCase = (s1, s2) -> s1.equalsIgnoreCase(s2);
System.out.println(equalsIgnoreCase.test("Hello", "HELLO"));  // true

// 判断两个整数,第一个是否能被第二个整除
BiPredicate<Integer, Integer> isDivisible = (a, b) -> a % b == 0;
System.out.println(isDivisible.test(10, 2));  // true

5. Function:转换数据(T → R)

5.1. Function介绍

Function<T, R> 是一个函数式接口,代表了一个接受一个参数并返回结果的函数。它的抽象方法是:

R apply(T t);

可以把它理解为一个转换操作:输入类型为 T,输出类型为 R。就像数学中的函数 y = f(x) 一样。

5.2. Function源码解读

核心是 apply 抽象方法,另外提供了两个默认方法 composeandThen 用于组合多个 Function,以及一个静态方法 identity 返回恒等函数。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * 将此函数应用于给定参数。
     *
     * @param t 函数参数
     * @return 函数结果
     */
    R apply(T t);

    /**
     * 返回一个组合函数,该函数首先将 before 函数应用于其输入,然后将此函数应用于结果。
     * 如果任一函数的求值抛出异常,则将其传递给组合函数的调用者。
     *
     * @param before 首先应用的函数
     * @return 组合函数,先应用 before 函数,再应用此函数
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * 返回一个组合函数,该函数首先将此函数应用于其输入,然后将 after 函数应用于结果。
     * 如果任一函数的求值抛出异常,则将其传递给组合函数的调用者。
     *
     * @param after 应用此函数后应用的函数
     * @return 组合函数,先应用此函数,再应用 after 函数
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * 返回一个总是返回其输入参数的函数。
     *
     * @param <T> 函数的输入和输出类型
     * @return 返回其输入的函数
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

5.3. 为什么使用Function

  1. 将行为参数化,实现数据转换

在传统编程中,我们经常需要将一种类型的数据转换为另一种类型。如果转换逻辑变化频繁,直接硬编码会导致代码重复和维护困难。使用 Function 可以将转换逻辑作为参数传递,让方法更加通用。

public class DataConverter {
    // 一个通用的转换方法,接受输入和转换函数
    public static <T, R> R convert(T input, Function<T, R> converter) {
        // 可以在这里添加一些通用逻辑,如日志、缓存等
        return converter.apply(input);
    }

    public static void main(String[] args) {
        // 场景1:将字符串转为整数(长度)
        Integer length = convert("Hello", String::length);
        System.out.println(length);  // 5

        // 场景2:将字符串转为大写
        String upper = convert("hello", String::toUpperCase);
        System.out.println(upper);   // HELLO

        // 场景3:将整数转为字符串
        String str = convert(123, Object::toString);
        System.out.println(str);     // "123"
    }
}
  1. 组合函数,形成数据处理管道

通过 composeandThen,我们可以将多个简单的 Function 组合成一个复杂的转换流程,让数据处理变得像流水线一样清晰。这两个默认方法都用于组合 Function,但执行顺序不同

  • andThen:先执行当前函数,再执行参数函数(从左到右)。
  • compose:先执行参数函数,再执行当前函数(从右到左)。
Function<String, String> trim = String::trim;          // 去除前后空格
Function<String, String> upper = String::toUpperCase;  // 转为大写

// andThen:先 trim,再 upper
String result1 = trim.andThen(upper).apply("  hello  ");
System.out.println(result1);  // "HELLO"

// compose:先 upper,再 trim(注意顺序!)
// 这里 upper.compose(trim) 实际上是先执行 trim,再执行 upper
String result2 = upper.compose(trim).apply("  hello  ");
System.out.println(result2);  // "HELLO"
  1. 与 Stream API 无缝集成

Stream.map(Function) 是 Function 最经典的应用场景。它可以将流中的每个元素进行转换,生成新的流。

List<Student> students = ...; // 假设已有学生列表

// 提取所有学生姓名
List<String> names = students.stream()
        .map(Student::getName)           // Function<Student, String>
        .collect(Collectors.toList());

// 链式转换:先取姓名,再取长度
List<Integer> nameLengths = students.stream()
        .map(Student::getName)
        .map(String::length)               // Function<String, Integer>
        .collect(Collectors.toList());

5.4. Funciton变体

除了通用的 Function<T, R>,Java 8 还提供了大量特化版本,用于避免装箱/拆箱、处理基本类型或接收两个参数。

接口名抽象方法说明
IntFunction<R>R apply(int value)输入为 int,输出为 R
LongFunction<R>R apply(long value)输入为 long,输出为 R
DoubleFunction<R>R apply(double value)输入为 double,输出为 R
ToIntFunction<T>int applyAsInt(T value)输入为 T,输出为 int
ToLongFunction<T>long applyAsLong(T value)输入为 T,输出为 long
ToDoubleFunction<T>double applyAsDouble(T value)输入为 T,输出为 double
IntToLongFunctionlong applyAsLong(int value)输入为 int,输出为 long
IntToDoubleFunctiondouble applyAsDouble(int value)输入为 int,输出为 double
LongToIntFunctionint applyAsInt(long value)输入为 long,输出为 int
LongToDoubleFunctiondouble applyAsDouble(long value)输入为 long,输出为 double
DoubleToIntFunctionint applyAsInt(double value)输入为 double,输出为 int
DoubleToLongFunctionlong applyAsLong(double value)输入为 double,输出为 long
// 输入 int,输出 String
IntFunction<String> intToString = i -> "Number: " + i;
System.out.println(intToString.apply(5));  // Number: 5

// 输入 String,输出 int
ToIntFunction<String> stringToInt = String::length;
int len = stringToInt.applyAsInt("hello");  // 5

// int 转 long
IntToLongFunction intToLong = i -> i * 1000L;
long l = intToLong.applyAsLong(5);  // 5000



// 双参数版本:BiFunction, 

双参数版本:BiFunction: BiFunction<T, U, R> 接收两个参数,返回一个结果。

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
    // 默认方法 andThen 类似
}

BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5));  // 8

// 合并两个字符串
BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
System.out.println(concat.apply("Hello ", "World"));  // Hello World

运算符版本:UnaryOperator 和 BinaryOperator

  • UnaryOperator<T> 继承自 Function<T, T>,表示输入输出类型相同的操作。例如 String::toUpperCase
  • BinaryOperator<T> 继承自 BiFunction<T, T, T>,表示两个相同类型参数,返回相同类型结果的操作。例如 Integer::sum
UnaryOperator<String> toUpper = String::toUpperCase;
System.out.println(toUpper.apply("hello"));  // HELLO

BinaryOperator<Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(3, 4));    // 12