用流式API优雅地在Java中组装数据

作为开发者,我们经常会遇到这样的场景:从数据库、微服务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();

这就是我们今天要构建的——一个支持链式调用的、类型安全的、高度灵活的流式数据组装器。

设计思想:流式API与函数式编程的碰撞

要实现上面那种优雅的链式调用,我们需要借助几个核心的设计思想:

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 condition): 定义T和U之间的匹配规则。
2、assemble(BiFunction assembler): 定义当T和U匹配时,如何将它们合并成一个新的对象R。这类似于SQL的INNER JOIN。它会返回一个全新的DataAssembler,让链式调用可以继续下去。
3、leftAssemble(BiFunction assembler): 类似于SQL的LEFT JOIN。即使在新数据U中找不到匹配项,原始数据T也会被保留。

/**
 * 代表一个“配对”状态的组装器,持有当前数据(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 condition): 保留原有的方法,用于复杂或动态的匹配逻辑,它将继续使用 O(N*M) 的慢速实现。
matchOn(Function keyExtractorT, Function keyExtractorU): 新增一个高性能的匹配方法。它接收两个函数,分别用于从 T 和 U 中提取类型为 K 的键。当两个键相等时,认为数据匹配。

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。

你可能感兴趣的:(用流式API优雅地在Java中组装数据)