作为开发者,我们经常会遇到这样的场景:从数据库、微服务API或不同的文件里获取到了几份数据,它们之间通过某些ID相互关联。我们的任务是将它们“拼接”成一个完整的、信息丰富的视图。
通常,我们的第一反应可能是这样:
// 丑陋的嵌套循环
List<FullInfo> result = new ArrayList<>();
for (User user : users) {
for (Order order : orders) {
if (user.getId() == order.getUserId()) {
for (Product product : products) {
if (order.getProductId() == product.getPid()) {
// 终于匹配上了!开始组装...
FullInfo info = new FullInfo(...);
result.add(info);
}
}
}
}
}
这种代码不仅丑陋,而且难以阅读和维护。随着数据源的增多,嵌套层级会越来越深,逻辑会越来越混乱。这就是所谓的“回调地狱”或“嵌套地狱”在数据处理中的翻版。
那么,有没有一种更优雅的方式呢?当然有!想象一下,如果你的代码可以像下面这样,如同一句流畅的英文句子,清晰地描述数据处理的每一步:
List<FullOrderInfo> finalData = DataAssembler
.source(users)
.data(orders)
.match((user, order) -> user.id() == order.userId())
.assemble((user, order) -> new UserOrder(user, order))
.data(products)
.match((userOrder, product) -> userOrder.order().productId() == product.pid())
.assemble((userOrder, product) -> new FullOrderInfo(...))
.get();
这就是我们今天要构建的——一个支持链式调用的、类型安全的、高度灵活的流式数据组装器。
要实现上面那种优雅的链式调用,我们需要借助几个核心的设计思想:
1、流式API (Fluent API): 每个方法都返回对象自身(或下一个状态的对象),从而可以像obj.method1().method2()一样连续调用。这是实现“句子般”代码的关键。
2、状态分离: 我们将组装过程拆分为不同的状态。例如,有一个持有单个数据集的初始状态,和一个持有两个待匹配数据集的“配对”状态。通过在不同状态间返回不同类型的对象,我们可以利用Java的类型系统来引导用户写出正确的调用链。
3、函数式接口: 我们不把匹配(match)和组装(assemble)的逻辑写死。而是使用Java 8的函数式接口(如BiPredicate和BiFunction),让调用者通过Lambda表达式自由注入这些核心逻辑。这使得我们的组装器通用且强大。
我们的设计包含两个核心类:DataAssembler和 PairedDataAssembler
DataAssembler:流程的起点和终点
这个类代表一个已经确定、类型统一的数据集。它是我们链式调用的“锚点”。
1、source(List data): 静态工厂方法,用于开始一个组装流程。
2、data(List nextData): 引入一个新的数据集,并将状态转换为“配- 对”状态,返回一个PairedDataAssembler实例。
3、get(): 结束流程,返回最终组装好的数据列表。
其他则支持常规的Stream流式操作
public class DataAssembler<T> {
private final List<T> currentAssembledData;
DataAssembler(List<T> data) {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null.");
}
this.currentAssembledData = data;
}
public static <T> DataAssembler<T> source(List<T> sourceData) {
return new DataAssembler<>(sourceData);
}
public <U> PairedDataAssembler<T, U> data(List<U> nextDataSource) {
return new PairedDataAssembler<>(this.currentAssembledData, nextDataSource);
}
/**
* 过滤当前数据集中的元素。
* @param predicate 过滤条件
* @return 一个持有过滤后数据的新DataAssembler实例
*/
public DataAssembler<T> filter(Predicate<T> predicate) {
List<T> filteredData = this.currentAssembledData.stream()
.filter(predicate)
.collect(Collectors.toList());
return new DataAssembler<>(filteredData);
}
/**
* 将当前数据集中的每个元素转换为一个新类型的元素。
* @param mapper 转换函数
* @param 新元素的类型
* @return 一个持有转换后数据的新DataAssembler实例
*/
public <R> DataAssembler<R> map(Function<T, R> mapper) {
List<R> mappedData = this.currentAssembledData.stream()
.map(mapper)
.collect(Collectors.toList());
return new DataAssembler<>(mappedData);
}
/**
* 对当前数据集进行排序。
* @param comparator 比较器
* @return 一个持有排序后数据的新DataAssembler实例
*/
public DataAssembler<T> sorted(Comparator<T> comparator) {
List<T> sortedData = this.currentAssembledData.stream()
.sorted(comparator)
.collect(Collectors.toList());
return new DataAssembler<>(sortedData);
}
/**
* 对当前数据集进行去重。
* 注意: T类型需要正确实现 equals() 和 hashCode() 方法。
* @return 一个持有去重后数据的新DataAssembler实例
*/
public DataAssembler<T> distinct() {
List<T> distinctData = this.currentAssembledData.stream()
.distinct()
.collect(Collectors.toList());
return new DataAssembler<>(distinctData);
}
/**
* 截取数据集的前N个元素。
* @param maxSize 要保留的最大元素数量
* @return 一个持有截取后数据的新DataAssembler实例
*/
public DataAssembler<T> limit(long maxSize) {
List<T> limitedData = this.currentAssembledData.stream()
.limit(maxSize)
.collect(Collectors.toList());
return new DataAssembler<>(limitedData);
}
/**
* 终端操作:对每个元素执行一个动作。
* 这是链式调用的终点之一。
*/
public void forEach(Consumer<T> action) {
this.currentAssembledData.forEach(action);
}
/**
* 终端操作:获取最终组装完成的数据。
* 这是链式调用的终点之一。
* @return 最终结果列表
*/
public List<T> get() {
return this.currentAssembledData;
}
}
PairedDataAssembler
这个类是整个设计的精髓。它代表一个“配对”好的中间状态,持有当前数据(List)和新引入的数据(List),等待用户定义如何处理它们。
1、match(BiPredicate
2、assemble(BiFunction
3、leftAssemble(BiFunction
/**
* 代表一个“配对”状态的组装器,持有当前数据(T)和新数据(U),等待定义匹配和组装规则。
* @param 当前数据集的元素类型
* @param 新数据集的元素类型
*/
public class PairedDataAssembler<T, U> {
private final List<T> currentData;
private final List<U> nextData;
private BiPredicate<T, U> matchCondition;
PairedDataAssembler(List<T> currentData, List<U> nextData) {
if (currentData == null || nextData == null) {
throw new IllegalArgumentException("Data sources cannot be null.");
}
this.currentData = currentData;
this.nextData = nextData;
}
/**
* 定义当前数据(T)与新数据(U)之间的匹配条件。
* @param condition 一个BiPredicate,接收(T, U),返回true表示匹配
* @return this,用于链式调用
*/
public PairedDataAssembler<T, U> match(BiPredicate<T, U> condition) {
this.matchCondition = condition;
return this;
}
/**
* 执行组装操作 (INNER JOIN)。
* 只有在两个数据源中都找到匹配项时,才会创建新的组装数据。
* @param assemblerFunction 一个BiFunction,接收(T, U),返回合并后的新对象R
* @param 组装后新对象的类型
* @return 一个新的DataAssembler,持有组装后的数据集List
*/
public <R> DataAssembler<R> assemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
List<R> newAssembledData = new ArrayList<>();
for (T currentItem : this.currentData) {
for (U nextItem : this.nextData) {
if (this.matchCondition.test(currentItem, nextItem)) {
newAssembledData.add(assemblerFunction.apply(currentItem, nextItem));
}
}
}
return new DataAssembler<>(newAssembledData);
}
/**
* 执行左连接组装操作 (LEFT JOIN)。
* 即使在新的数据源中找不到匹配项,原始数据项也总是会被用于组装。
* @param assemblerFunction 一个BiFunction,接收(T, U),当没有匹配时U为null。
* @param 组装后新对象的类型
* @return 一个新的DataAssembler,持有组装后的数据集List
*/
public <R> DataAssembler<R> leftAssemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
List<R> newAssembledData = new ArrayList<>();
for (T currentItem : this.currentData) {
List<U> matchedItems = this.nextData.stream()
.filter(nextItem -> this.matchCondition.test(currentItem, nextItem))
.collect(Collectors.toList());
if (matchedItems.isEmpty()) {
newAssembledData.add(assemblerFunction.apply(currentItem, null));
} else {
for (U matchedItem : matchedItems) {
newAssembledData.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
}
return new DataAssembler<>(newAssembledData);
}
private void validateState() {
if (this.matchCondition == null) {
throw new IllegalStateException("Must call match() before calling assemble() or leftAssemble().");
}
}
}
assemble 和 leftAssemble 方法内部都使用了嵌套循环,导致时间复杂度为 O(N * M),其中 N 是 currentData 的大小,M 是 nextData 的大小。当数据集很大时,这会变得非常慢。
我们可以通过将第二个数据集(nextData)预处理成一个 Map 来显著提升性能。这个 Map 的键(Key)应该是用于匹配的字段,值(Value)是与该键对应的所有数据项列表(因为可能存在一对多的关系)。
这样,对于 currentData 中的每个元素,我们不再需要遍历整个 nextData,而是可以直接从 Map 中以 O(1) 的时间复杂度(平均情况)查找匹配项。整个组装过程的时间复杂度将从 O(N * M) 降低到 O(N + M)(其中 O(M) 用于构建Map,O(N) 用于遍历和查找)。
设计思路
1、引入Key提取器: 为了让 PairedDataAssembler 知道如何从新数据(U)中提取用于构建 Map 的键,我们需要一个新的方法或参数。一个优雅的方式是在 match 方法中同时提供两个 Function,一个用于从当前数据(T)中提取键,另一个用于从新数据(U)中提取键。
2、新的 match 方法: 我们将重载 match 方法。
match(BiPredicate
matchOn(Function
3、修改 assemble 逻辑: assemble 和 leftAssemble 方法需要能够根据 match 方法的类型来选择不同的执行策略(快速的Map查找或慢速的嵌套循环)。
优化后的代码实现
/**
* 代表一个“配对”状态的组装器,持有当前数据(T)和新数据(U),等待定义匹配和组装规则。
* @param 当前数据集的元素类型
* @param 新数据集的元素类型
*/
public class PairedDataAssembler<T, U> {
private final List<T> currentData;
private final List<U> nextData;
// 策略1: 慢速但灵活的 BiPredicate
private BiPredicate<T, U> slowMatchCondition;
// 策略2: 高性能的基于Key的匹配
private Function<T, ?> keyExtractorT;
private Function<U, ?> keyExtractorU;
private Map<Object, List<U>> lookupMapU; // 预处理后的Map
// 包级私有构造函数
PairedDataAssembler(List<T> currentData, List<U> nextData) {
if (currentData == null || nextData == null) {
throw new IllegalArgumentException("Data sources cannot be null.");
}
this.currentData = currentData;
this.nextData = nextData;
}
/**
* [慢速] 定义一个通用的匹配条件。时间复杂度 O(N*M)。
* @param condition 一个BiPredicate,返回true表示匹配
* @return this
*/
public PairedDataAssembler<T, U> match(BiPredicate<T, U> condition) {
this.slowMatchCondition = condition;
this.keyExtractorT = null; // 清除快速匹配策略
this.keyExtractorU = null;
this.lookupMapU = null;
return this;
}
/**
* [高性能] 定义基于Key的匹配条件。时间复杂度 O(N+M)。
* 数据将通过比较从两个数据集中提取的Key是否相等来进行匹配。
* @param keyExtractorT 用于从当前数据(T)中提取Key的函数
* @param keyExtractorU 用于从新数据(U)中提取Key的函数
* @param Key的类型
* @return this
*/
public <K> PairedDataAssembler<T, U> matchOn(Function<T, K> keyExtractorT, Function<U, K> keyExtractorU) {
this.keyExtractorT = keyExtractorT;
this.keyExtractorU = keyExtractorU;
// 预处理nextData,构建查找Map
this.lookupMapU = this.nextData.stream()
.collect(Collectors.groupingBy(keyExtractorU));
this.slowMatchCondition = null; // 清除慢速匹配策略
return this;
}
/**
* 执行组装操作 (INNER JOIN)。
* 会自动选择高性能或慢速策略。
*/
public <R> DataAssembler<R> assemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
List<R> newAssembledData;
if (isFastPathEnabled()) {
newAssembledData = assembleWithFastPath(assemblerFunction);
} else {
newAssembledData = assembleWithSlowPath(assemblerFunction);
}
return new DataAssembler<>(newAssembledData);
}
/**
* 执行左连接组装操作 (LEFT JOIN)。
* 会自动选择高性能或慢速策略。
*/
public <R> DataAssembler<R> leftAssemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
List<R> newAssembledData;
if (isFastPathEnabled()) {
newAssembledData = leftAssembleWithFastPath(assemblerFunction);
} else {
newAssembledData = leftAssembleWithSlowPath(assemblerFunction);
}
return new DataAssembler<>(newAssembledData);
}
// --- 快速路径实现 (O(N+M)) ---
private <R> List<R> assembleWithFastPath(BiFunction<T, U, R> assemblerFunction) {
List<R> results = new ArrayList<>();
for (T currentItem : this.currentData) {
Object key = this.keyExtractorT.apply(currentItem);
List<U> matchedItems = this.lookupMapU.getOrDefault(key, Collections.emptyList());
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
return results;
}
private <R> List<R> leftAssembleWithFastPath(BiFunction<T, U, R> assemblerFunction) {
List<R> results = new ArrayList<>();
for (T currentItem : this.currentData) {
Object key = this.keyExtractorT.apply(currentItem);
List<U> matchedItems = this.lookupMapU.getOrDefault(key, Collections.emptyList());
if (matchedItems.isEmpty()) {
results.add(assemblerFunction.apply(currentItem, null));
} else {
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
}
return results;
}
// --- 慢速路径实现 (O(N*M)) ---
private <R> List<R> assembleWithSlowPath(BiFunction<T, U, R> assemblerFunction) {
List<R> results = new ArrayList<>();
for (T currentItem : this.currentData) {
for (U nextItem : this.nextData) {
if (this.slowMatchCondition.test(currentItem, nextItem)) {
results.add(assemblerFunction.apply(currentItem, nextItem));
}
}
}
return results;
}
private <R> List<R> leftAssembleWithSlowPath(BiFunction<T, U, R> assemblerFunction) {
List<R> results = new ArrayList<>();
for (T currentItem : this.currentData) {
List<U> matchedItems = this.nextData.stream()
.filter(nextItem -> this.slowMatchCondition.test(currentItem, nextItem))
.collect(Collectors.toList());
if (matchedItems.isEmpty()) {
results.add(assemblerFunction.apply(currentItem, null));
} else {
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
}
return results;
}
private boolean isFastPathEnabled() {
return this.keyExtractorT != null && this.keyExtractorU != null;
}
private void validateState() {
if (!isFastPathEnabled() && this.slowMatchCondition == null) {
throw new IllegalStateException("Must call match() or matchOn() before calling assemble() or leftAssemble().");
}
}
}
让我们用一个经典的电商场景来检验我们的成果。
public class GenericAssemblerDemo {
// 原始数据模型
record User(int id, String name, String city) {}
record Order(int orderId, int userId, int productId, double amount) {}
record Product(int pid, String productName) {}
// 中间组装结果模型
record UserOrder(User user, Order order) {}
// 最终组装结果模型
record FullOrderInfo(int userId, String userName, String city, int orderId, double amount, String productName) {
@Override
public String toString() {
return String.format(
"FullOrderInfo[user=%s(%d), city=%s, orderId=%d, product=%s, amount=%.2f]",
userName, userId, city, orderId, productName, amount
);
}
}
public static void main(String[] args) {
// 1. 准备原始数据
List<User> users = Arrays.asList(
new User(1, "Alice", "New York"),
new User(2, "Bob", "London"),
new User(3, "Charlie", "Paris") // 该用户没有订单
);
List<Order> orders = Arrays.asList(
new Order(101, 1, 10, 150.0),
new Order(102, 2, 20, 200.0),
new Order(103, 1, 20, 220.0)
);
List<Product> products = Arrays.asList(
new Product(10, "Laptop"),
new Product(20, "Mouse"),
new Product(30, "Keyboard") // 该产品没有被购买
);
System.out.println("--- 泛型链式组装 (INNER JOIN) ---");
// 2. 使用泛型DataAssembler进行链式组装
List<FullOrderInfo> finalData = DataAssembler
.source(users) // 返回 DataAssembler
.data(orders) // 返回 PairedDataAssembler
.match((user, order) -> user.id() == order.userId())
.assemble((user, order) -> new UserOrder(user, order)) // 返回 DataAssembler
.data(products) // 返回 PairedDataAssembler
.match((userOrder, product) -> userOrder.order().productId() == product.pid())
.assemble((userOrder, product) -> new FullOrderInfo( // 返回 DataAssembler
userOrder.user().id(),
userOrder.user().name(),
userOrder.user().city(),
userOrder.order().orderId(),
userOrder.order().amount(),
product.productName()
))
.get(); // 返回 List
finalData.forEach(System.out::println);
System.out.println("\n--- 泛型链式组装 (LEFT JOIN) ---");
// 3. 使用 leftAssemble 保留所有用户
List<FullOrderInfo> leftJoinData = DataAssembler
.source(users) // -> DataAssembler
.data(orders) // -> PairedDataAssembler
.match((user, order) -> user.id() == order.userId())
// 始终返回统一的中间类型 UserOrder。
// 如果没有匹配的订单,则order字段为null。
.leftAssemble((user, order) -> new UserOrder(user, order)) // -> DataAssembler
.data(products) // -> PairedDataAssembler
// 匹配时,必须先检查中间对象中的order是否存在。
.match((userOrder, product) ->
userOrder.order() != null && // 关键检查!
userOrder.order().productId() == product.pid()
)
//在最终组装时,处理所有可能的情况。
.leftAssemble((userOrder, product) -> {
User user = userOrder.user();
Order order = userOrder.order();
// 情况 A: 用户有订单,且产品信息也找到了
if (order != null && product != null) {
return new FullOrderInfo(
user.id(), user.name(), user.city(),
order.orderId(), order.amount(),
product.productName()
);
}
// 情况 B: 用户有订单,但产品信息未找到 (product is null)
else if (order != null) {
return new FullOrderInfo(
user.id(), user.name(), user.city(),
order.orderId(), order.amount(),
"Product Not Found" // 或者 null, 或其他标记
);
}
// 情况 C: 用户没有订单 (order is null, product will also be null)
else {
return new FullOrderInfo(
user.id(), user.name(), user.city(),
0, 0.0, "N/A"
);
}
}) // -> DataAssembler
.get(); // -> List
leftJoinData.forEach(System.out::println);
System.out.println("--- 高性能组装 (matchOn) 演示 ---");
List<FullOrderInfo> result = DataAssembler
.source(users)
.data(orders)
// 使用高性能的 matchOn 方法
.matchOn(User::id, Order::userId)
.leftAssemble((user, order) -> new UserOrder(user, order))
.data(products)
.matchOn(
userOrder -> userOrder.order() != null ? userOrder.order().productId() : null,
Product::pid
)
.leftAssemble((userOrder, product) -> {
User user = userOrder.user();
Order order = userOrder.order();
if (order != null && product != null) {
return new FullOrderInfo(user.id(), user.name(), user.city(), order.orderId(), order.amount(), product.productName());
} else if (order != null) {
return new FullOrderInfo(user.id(), user.name(), user.city(), order.orderId(), order.amount(), "Product Not Found");
} else {
return new FullOrderInfo(user.id(), user.name(), user.city(), 0, 0.0, "N/A");
}
})
.sorted(Comparator.comparing(FullOrderInfo::userName))
.get();
result.forEach(System.out::println);
}
}
将数据源从固定的 List变为通过函数式接口懒加载(Lazy Loading),意味着我们的数据处理管道可以处理流式数据或分批加载的数据,从而极大地降低内存消耗,并能处理远超内存大小的数据集。
设计思路
1、数据源接口化: 将数据源从具体的 List抽象为一个函数式接口,我们称之为 DataProvider。这个接口的核心是提供一种获取数据的方式。最简单的形式是 Supplier,即一个能提供一批数据的方法。
2、触发式执行: 整个链式调用在定义阶段(调用 source, data, match 等)将不再执行任何实际的数据处理。它只是在构建一个执行计划(Execution Plan)。只有当终端操作(如 get(), forEach())被调用时,才会真正触发数据提供者(DataProvider)去加载数据,并按照计划执行整个流程。
3、状态管理: DataAssembler 和 PairedDataAssembler 不再直接持有 List,而是持有 DataProvider 或描述如何生成数据的函数。
定义 DataProvider
我们可以直接使用 java.util.function.Supplier,但为了语义清晰,可以自定义一个接口。
@FunctionalInterface
public interface DataProvider<T> {
List<T> get();
}
重构 DataAssembler
这个类现在代表一个“计算描述”,而不是一个具体的数据集。
public class DataAssembler<T> {
private final DataProvider<T> dataProvider;
private DataAssembler(DataProvider<T> dataProvider) {
this.dataProvider = dataProvider;
}
public static <T> DataAssembler<T> source(DataProvider<T> provider) {
return new DataAssembler<>(provider);
}
public static <T> DataAssembler<T> source(List<T> sourceData) {
return new DataAssembler<>(() -> sourceData);
}
public <U> PairedDataAssembler<T, U> data(DataProvider<U> nextProvider) {
return new PairedDataAssembler<>(this.dataProvider, nextProvider);
}
public <U> PairedDataAssembler<T, U> data(List<U> nextData) {
return new PairedDataAssembler<>(this.dataProvider, () -> nextData);
}
// --- 流式操作 (懒加载) ---
public DataAssembler<T> filter(Predicate<T> predicate) {
return new DataAssembler<>(() -> {
List<T> data = this.dataProvider.get();
return data.stream().filter(predicate).collect(Collectors.toList());
});
}
public <R> DataAssembler<R> map(Function<T, R> mapper) {
return new DataAssembler<>(() -> {
List<T> data = this.dataProvider.get();
return data.stream().map(mapper).collect(Collectors.toList());
});
}
public DataAssembler<T> sorted(Comparator<T> comparator) {
return new DataAssembler<>(() -> {
List<T> data = this.dataProvider.get();
return data.stream().sorted(comparator).collect(Collectors.toList());
});
}
public DataAssembler<T> distinct() {
return new DataAssembler<>(() -> this.dataProvider.get().stream().distinct().collect(Collectors.toList()));
}
public DataAssembler<T> limit(long maxSize) {
return new DataAssembler<>(() -> this.dataProvider.get().stream().limit(maxSize).collect(Collectors.toList()));
}
public void forEach(Consumer<T> action) {
get().forEach(action);
}
public List<T> get() {
return this.dataProvider.get();
}
}
重构 PairedDataAssembler
assemble 和 leftAssemble 方法现在不再立即执行,而是返回一个新的 DataAssembler,这个 DataAssembler 内部的 DataProvider 封装了组装逻辑。
public class PairedDataAssembler<T, U> {
private final DataProvider<T> providerT;
private final DataProvider<U> providerU;
private BiPredicate<T, U> slowMatchCondition;
private Function<T, ?> keyExtractorT;
private Function<U, ?> keyExtractorU;
PairedDataAssembler(DataProvider<T> providerT, DataProvider<U> providerU) {
this.providerT = providerT;
this.providerU = providerU;
}
public PairedDataAssembler<T, U> match(BiPredicate<T, U> condition) {
this.slowMatchCondition = condition;
this.keyExtractorT = null;
this.keyExtractorU = null;
return this;
}
public <K> PairedDataAssembler<T, U> matchOn(Function<T, K> keyExtractorT, Function<U, K> keyExtractorU) {
this.keyExtractorT = keyExtractorT;
this.keyExtractorU = keyExtractorU;
this.slowMatchCondition = null;
return this;
}
public <R> DataAssembler<R> assemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
return DataAssembler.source(() -> {
if (isFastPathEnabled()) {
return assembleWithFastPath(assemblerFunction);
} else {
return assembleWithSlowPath(assemblerFunction);
}
});
}
public <R> DataAssembler<R> leftAssemble(BiFunction<T, U, R> assemblerFunction) {
validateState();
return DataAssembler.source(() -> {
if (isFastPathEnabled()) {
return leftAssembleWithFastPath(assemblerFunction);
} else {
return leftAssembleWithSlowPath(assemblerFunction);
}
});
}
private <R> List<R> assembleWithFastPath(BiFunction<T, U, R> assemblerFunction) {
List<T> currentData = providerT.get();
List<U> nextData = providerU.get();
Map<Object, List<U>> lookupMap = buildLookupMap(nextData);
List<R> results = new ArrayList<>();
for (T currentItem : currentData) {
Object key = keyExtractorT.apply(currentItem);
List<U> matchedItems = lookupMap.getOrDefault(key, Collections.emptyList());
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
return results;
}
private <R> List<R> leftAssembleWithFastPath(BiFunction<T, U, R> assemblerFunction) {
List<T> currentData = providerT.get();
List<U> nextData = providerU.get();
Map<Object, List<U>> lookupMap = buildLookupMap(nextData);
List<R> results = new ArrayList<>();
for (T currentItem : currentData) {
Object key = keyExtractorT.apply(currentItem);
List<U> matchedItems = lookupMap.getOrDefault(key, Collections.emptyList());
if (matchedItems.isEmpty()) {
results.add(assemblerFunction.apply(currentItem, null));
} else {
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
}
return results;
}
private <R> List<R> assembleWithSlowPath(BiFunction<T, U, R> assemblerFunction) {
List<T> currentData = providerT.get();
List<U> nextData = providerU.get();
List<R> results = new ArrayList<>();
for (T currentItem : currentData) {
for (U nextItem : nextData) {
if (slowMatchCondition.test(currentItem, nextItem)) {
results.add(assemblerFunction.apply(currentItem, nextItem));
}
}
}
return results;
}
private <R> List<R> leftAssembleWithSlowPath(BiFunction<T, U, R> assemblerFunction) {
List<T> currentData = providerT.get();
List<U> nextData = providerU.get();
List<R> results = new ArrayList<>();
for (T currentItem : currentData) {
List<U> matchedItems = nextData.stream()
.filter(nextItem -> slowMatchCondition.test(currentItem, nextItem))
.collect(Collectors.toList());
if (matchedItems.isEmpty()) {
results.add(assemblerFunction.apply(currentItem, null));
} else {
for (U matchedItem : matchedItems) {
results.add(assemblerFunction.apply(currentItem, matchedItem));
}
}
}
return results;
}
@SuppressWarnings("unchecked")
private Map<Object, List<U>> buildLookupMap(List<U> data) {
Function<U, ?> extractor = this.keyExtractorU;
return data.stream().collect(Collectors.groupingBy((Function<U, Object>) extractor));
}
private boolean isFastPathEnabled() {
return keyExtractorT != null && keyExtractorU != null;
}
private void validateState() {
if (!isFastPathEnabled() && slowMatchCondition == null) {
throw new IllegalStateException("在调用 assemble() 或左 Assemble() 之前,必须调用 match() 或 match On().");
}
}
}
我们从一个常见的数据处理痛点出发,设计并实现了一个优雅、类型安全、可扩展的流式数据组装器。它将复杂、易错的嵌套循环,转换成了清晰、可维护的链式调用。
这个模式的优点:
高可读性: 代码即文档。
类型安全: 在编译期捕获错误。
灵活性: 匹配和组装逻辑完全由用户自定义。
可扩展性: 可以轻松添加更多操作,如filter(), sort()或rightAssemble。