Java 根据对象属性连接两个 List

Last Modified: 2023/10/23

概述

有时候我们需要对两个 List 或者 Collection 中的对象根据某个属性建立关联,关联的方式就像是数据库中的一对一或者一对多。使用 sql 语句可以直接完成关联,但是有时候我们希望查出两个 List,然后使用 Java 代码手动关联,这些代码往往是样板化的,能不能写一个相对通用的方法呢?这便是本文的目标。

场景描述

假设有两张表:user 表和 order 表,表中的数据如下:


Java 代码中可以分别定义 User 和 Order 作为表的实体类:

// 省略 getter and setter
// 省略 constructor
public class User {
    private int id;
    private String name;
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

public class Order {
    private int id;
    private int userId;
    private String product;
    
    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", userId=" + userId +
                ", product='" + product + '\'' +
                '}';
    }
}

Java 实现一对一 Join

首先我们假设 User 和 Order 对象是一对一的关系,为了找到 User 和 Order 的关系,通常的做法如下:

// 首先建立 userId 和 Order 对象的映射关系
Map<Integer, Order> userId2OrderMap = orders.stream().collect(Collectors.toMap(Order::getUserId, e -> e));
for (User user : users) {
    int userId = user.getId();
    // 根据 user 对象的 id 去 userId2OrderMap 中找到对应的 order 对象
    Order order = userId2OrderMap.get(userId);
}

我们可以抽象一个 join 方法来简化这段样板化的代码

import java.util.Collection;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

class JoinUtil {
    public static <T, E, K> void join(Collection<T> main,
                                      Collection<E> sub,
                                      Function<? super T, K> mainKeyMapper,
                                      Function<? super E, K> subKeyMapper,
                                      BiConsumer<? super T, ? super E> pairHandler) {
        Map<K, E> map = sub.stream().collect(Collectors.toMap(subKeyMapper, e -> e, (a, b) -> a));
        for (T t : main) {
            K k = mainKeyMapper.apply(t);
            E e = map.get(k);
            pairHandler.accept(t, e);
        }
    }
}

完整代码,请移步 Github 查看。

mainKeyMapper 作用于 main collection 中的每个元素,目的是获取连接字段的值;subKeyMapper 作用于 sub collection,用于是用于建立一个 map,map 的 key 是通过 subKeyMapper 作用于 sub collection 中的元素获得的,value 则是被作用的元素本身。

pairHandler 是一个回调方法,可以传递一个 lambda 表达式来接收一对匹配的对象。下面我们来看看它的用法。

public static void main(String[] args) {
  List<User> users = new ArrayList<>();
  users.add(new User(1, "lucy"));
  users.add(new User(2, "john"));
  
  List<Order> orders = new ArrayList<>();
  orders.add(new Order(1, 2, "laptop"));
  orders.add(new Order(2, 1, "mouse"));
  
  JoinUtil.join(users, orders, User::getId, Order::getUserId, (user, order) -> {
      System.out.println(user + "matched order is: " + order);
  });
}

// 输出如下
User{id=1, name='lucy'}matched order is: Order{id=2, userId=1, product='mouse'}
User{id=2, name='john'}matched order is: Order{id=1, userId=2, product='laptop'}

Java 实现一对多 Join

实现一对多也很简单,由于是一对多,因此 BiConsumer 的第二个泛型参数为 List。这里直接给出代码实现:

public static <T, E, K> void joinM(Collection<T> main,
                                  Collection<E> sub,
                                  Function<? super T, K> mainKeyMapper,
                                  Function<? super E, K> subKeyMapper,
                                  BiConsumer<? super T, List<? super E>> pairHandler) {
    Map<K, List<E>> map = sub.stream().collect(Collectors.groupingBy(subKeyMapper));
    for (T t : main) {
        K k = mainKeyMapper.apply(t);
        List<E> e = map.get(k);
        pairHandler.accept(t, e);
    }
}

下面是 joinM 的使用方法,users 和 orders 和 上面的数据相同,因此不再赘述。

JoinUtil.joinM(users, orders, User::getId, Order::getUserId, (user, matchOrders) -> {
      System.out.println(user + "matched orders is: " + matchOrders);
});

// 输出如下
User{id=1, name='lucy'} matched orders is: [Order{id=2, userId=1, product='mouse'}]
User{id=2, name='john'} matched orders is: [Order{id=1, userId=2, product='laptop'}]

总结

本文介绍了如何使用 Java 代码实现类似数据中的一对一和一对多连接,并给出了两个 join util 方法的实现,利用这两个方法可以帮助我们消除样板化代码。

有问题吗?点此反馈!

温馨提示:反馈需要登录