1. 泛型通配符
泛型允许在定义类、接口和方法时使用类型参数,使代码能够处理多种类型而无需重写。但有时我们并不关心具体的类型参数,只想表达某种类型范围,这时就需要使用通配符 ?。
例如,List<?> 表示元素类型未知的列表。但单纯的无界通配符无法表达“某种类型的子类”或“某种类型的父类”,因此引入了有界通配符:? extends T 和 ? super T。
1.1. ? extends T(上界通配符)
含义:? extends T 表示类型参数必须是 T 或 T 的子类(包括 T 本身)。例如,List<? extends Number> 可以接受 List<Number>、List<Integer>、List<Double> 等,因为 Integer 和 Double 都是 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使得泛型类型在子类型关系上具有协变性。即,如果Integer是Number的子类,那么List<Integer>可以被视为List<? extends Number>的子类型。这弥补了 Java 泛型默认是不可变的限制。"Java 泛型默认是不可变的"(Invariance)指的是:即使类型参数之间存在继承关系,泛型类型之间也不存在继承关系。例如,具体来说,如果String是Object的子类,但List<String>并不是List<Object>的子类。这两个类型被视为完全无关的类型,不能互相赋值;
典型应用场景:生产者,当你需要从一个数据结构中读取数据,并将这些数据作为 T 类型处理时,使用 ? extends T。因为你只知道读取的对象是 T 的子类,所以可以安全地以 T 类型处理。
1.2. ? super T(下界通配符)
含义:? super T 表示类型参数必须是 T 或 T 的父类(包括 T 本身)。例如,List<? super Integer> 可以接受 List<Integer>、List<Number>、List<Object> 等,因为 Integer 的父类包括 Number 和 Object。
作用:
- 只写安全:当你使用
? 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使得泛型类型在子类型关系上具有逆变性。即,如果Number是Integer的父类,那么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, 消费者将元素放入数据结构中。例如,向集合中添加元素。 - 如果既生产又消费,则应该使用精确的类型参数,不使用通配符。
示例说明:
- 生产者场景:从集合中复制元素:
- 假设有一个方法,需要将源集合中的所有元素复制到目标集合中。源集合是生产者(提供元素),目标集合是消费者(接收元素)。
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)
- 生产者场景:从集合中取数据
Collections.max方法用于返回集合的最大元素,它只从集合中读取数据,因此使用? extends 。T。coll是生产者,我们从中读取元素并比较。
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
// 实现
}
- 消费者场景:向集合中添加元素
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.forEach、Optional.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?
- 作为参数传递,实现策略模式
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));
- 链式组合,构建处理流水线
利用 andThen,可以把多个 Consumer 串成一条流水线,数据依次经过每个环节。这在中间件、拦截器设计中非常实用。
- 与 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); // 最终消费
- 订单处理流水线
假设我们有一个订单处理流程,需要依次执行:记录日志 → 保存数据库 → 发送消息通知。
// 定义三个消费逻辑
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);
- 其他案例应用
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 |
| IntConsumer | void accept(int value) | 接收一个 int 值 |
| LongConsumer | void 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 的妙处。
- 延迟执行(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 就能避免无谓的开销。
- 解耦对象的创建
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); // 构造器引用
- 作为工厂,生成一系列对象
Supplier 可以用来生成无限的数据流,配合 Stream.generate() 非常方便。
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
- 与 Optional 结合,实现优雅的空值处理
除了 orElseGet,还可以在需要时才从 Supplier 获取默认值,避免提前创建对象。
public String getUserName(Optional<User> optional) {
return optional.map(User::getName)
.orElseGet(() -> fetchDefaultNameFromDB());
}
- 其他案例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 接口,避免自动装箱/拆箱带来的性能开销。
| 接口名 | 抽象方法 | 说明 |
|---|---|---|
BooleanSupplier | boolean getAsBoolean() | 返回 boolean 类型的结果 |
BooleanSupplier | boolean getAsBoolean() | 返回 boolean 类型的结果 |
IntSupplier | int getAsInt() | 返回 int 类型的结果 |
package java.util.function;
/**
* 表示结果提供者。
*/
@FunctionalInterface
public interface Supplier<T> {
T get();
}
4. Predicate:条件判断(T → boolean)
4.1. Predicate介绍
Predicate<T> 是一个函数式接口,它代表了一个谓词(布尔值函数) ,即对给定参数进行判断,返回 true 或 false。它的抽象方法是:
boolean test(T t);
你可以把它理解为一个条件表达式,专门用来做过滤或匹配。
4.2. Predicate源码解读
除了核心的 test 方法,还提供了三个默认方法:and、or、negate,用于组合多个 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
- 将条件逻辑参数化
传统编程中,我们经常需要根据不同的条件执行不同的操作。如果把这些条件硬编码在方法内部,代码会变得僵硬。使用 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;
}
- 组合逻辑,避免重复代码
通过 and、or、negate 方法,我们可以将多个简单的 Predicate 组合成复杂的条件,而无需编写大量 if-else 嵌套。
- 与 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());
- 其他代码案例
// 基本用法: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 接口,避免自动装箱/拆箱带来的性能开销。
| 接口名 | 抽象方法 | 说明 |
|---|---|---|
IntPredicate | boolean test(int value) | 判断 int 值 |
LongPredicate | boolean test(long value) | 判断 long 值 |
DoublePredicate | boolean test(double value) | 判断 double 值 |
BiPredicate | boolean 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 抽象方法,另外提供了两个默认方法 compose 和 andThen 用于组合多个 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
- 将行为参数化,实现数据转换
在传统编程中,我们经常需要将一种类型的数据转换为另一种类型。如果转换逻辑变化频繁,直接硬编码会导致代码重复和维护困难。使用 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"
}
}
- 组合函数,形成数据处理管道
通过 compose 和 andThen,我们可以将多个简单的 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"
- 与 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 |
IntToLongFunction | long applyAsLong(int value) | 输入为 int,输出为 long |
IntToDoubleFunction | double applyAsDouble(int value) | 输入为 int,输出为 double |
LongToIntFunction | int applyAsInt(long value) | 输入为 long,输出为 int |
LongToDoubleFunction | double applyAsDouble(long value) | 输入为 long,输出为 double |
DoubleToIntFunction | int applyAsInt(double value) | 输入为 double,输出为 int |
DoubleToLongFunction | long 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