使用Java流合并列表中相同对象下的列表 [英] Merging lists under same objects in a list using Java streams

查看:115
本文介绍了使用Java流合并列表中相同对象下的列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个对象如下:

public class A {
    private Integer id;
    private String name;
    private List<B> list;

    public A(Integer id, String name, List<B> list) {
        this.id = id;
        this.name = name;
        this.list = list;
    }

    //getters and setters
}

public class B {
    private Integer id;
    private String name;

    public B(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    //getters and setters
}

因此,A持有B的列表,并且有一个填充的A列表如下:

So, A holds a list of B and there is a list of A populated as follows:

    List<A> list = new ArrayList<>();
    list.add(new A(1, "a_one", Arrays.asList(new B(1, "b_one"), new B(2, "b_two"))));
    list.add(new A(2, "a_two", Arrays.asList(new B(2, "b_two"))));
    list.add(new A(1, "a_one", Arrays.asList(new B(3, "b_three"))));
    list.add(new A(2, "a_two", Arrays.asList(new B(4, "b_four"), new B(5, "b_five"))));
    list.add(new A(3, "a_three", Arrays.asList(new B(4, "b_four"), new B(5, "b_five"))));

我想通过合并具有相同ID的对象来获取新列表。结果列表必须如下:

I want to acquire a new list by merging A objects with same ids. Result list must be like that:

[
    A(1, a_one, [B(1, b_one), B(2, b_two), B(3, b_three)]),
    A(2, a_two, [B(2, b_two), B(4, b_four), B(5, b_five)]),
    A(3, a_three, [B(4, b_four), B(5, b_five)])
]

我确实设法将列表与以下代码合并:

I did manage to merge the list with the following code:

List<A> resultList = new ArrayList<>();
list.forEach(a -> {
    if (resultList.stream().noneMatch(ai -> ai.getId().equals(a.getId()))) {
        a.setList(list.stream().filter(ai -> ai.getId().equals(a.getId()))
                .flatMap(ai -> ai.getList().stream()).collect(Collectors.toList()));
        resultList.add(a);
    }
});

我的问题是,有没有正确的方法通过使用流收集器来做到这一点?

My question is, is there any proper way to do this by using stream collectors?

推荐答案

如果您不想使用额外的功能,您可以执行以下操作,它可读且易于理解,首先按ID分组,创建一个新对象,列表中有第一个元素,然后加入所有B类,最后收集A。

If you don't want to use extra functions you can do the following, it's readable and easy to understand, first group by id, create a new object with the first element in the list and then join all the B's classes to finally collect the A's.

List<A> result = list.stream()
    .collect(Collectors.groupingBy(A::getId))
    .values().stream()
    .map(grouped -> new A(grouped.get(0).getId(), grouped.get(0).getName(),
            grouped.stream().map(A::getList).flatMap(List::stream)
                .collect(Collectors.toList())))
    .collect(Collectors.toList());

另一种方法是使用二元运算符和收藏家。 groupingBy 方法。这里使用java 8可选类在 fst 为空时第一次创建新的A.

Another way is to use a binary operator and the Collectors.groupingBy method. Here you use the java 8 optional class to create the new A the first time when fst is null.

BinaryOperator<A> joiner = (fst, snd) -> Optional.ofNullable(fst)
    .map(cur -> { cur.getList().addAll(snd.getList()); return cur; })
    .orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));

Collection<A> result = list.stream()
    .collect(Collectors.groupingBy(A::getId, Collectors.reducing(null, joiner)))
    .values();

如果你不喜欢在短lambda中使用return(看起来不太好) only选项只是一个过滤器,因为java没有提供另一种方法,比如stream's peek(注意:某些IDE突出显示'简化'表达式,并且不应该在过滤器中进行突变[但我认为在地图中也没有]。)

If you don't like to use return in short lambdas (doesn't look that well) the only option is a filter because java does not provide another method like stream's peek (note: some IDEs highlight to 'simplify' the expression and mutations shouldn't be made in filter [but i think in maps neither]).

BinaryOperator<A> joiner = (fst, snd) -> Optional.ofNullable(fst)
    .filter(cur -> cur.getList().addAll(snd.getList()) || true)
    .orElseGet(() -> new A(snd.getId(), snd.getName(), new ArrayList<>(snd.getList())));

您也可以使用此连接作为通用方法,并创建一个从左到右的reducer与一个消费者允许加入用初始化函数创建的新的可变对象。

You can also use this joiner as a generic method and create a left to right reducer with a consumer that allows to join the new mutable object created with the initializer function.

public class Reducer {
    public static <A> Collector<A, ?, A> reduce(Function<A, A> initializer, 
                                                BiConsumer<A, A> combiner) {
        return Collectors.reducing(null, (fst, snd) -> Optional.ofNullable(fst)
            .map(cur -> { combiner.accept(cur, snd); return cur; })
            .orElseGet(() -> initializer.apply(snd)));
    }
    public static <A> Collector<A, ?, A> reduce(Supplier<A> supplier, 
                                                BiConsumer<A, A> combiner) {
        return reduce((ign) -> supplier.get(), combiner);
    }
}

并像

Collection<A> result = list.stream()
    .collect(Collectors.groupingBy(A::getId, Reducer.reduce(
        (cur) -> new A(cur.getId(), cur.getName(), new ArrayList<>(cur.getList())),
        (fst, snd) -> fst.getList().addAll(snd.getList())
    ))).values();

或者如果你有一个初始化集合的空构造函数

Or like if you have an empty constructor that initializes the collections

Collection<A> result = list.stream()
    .collect(Collectors.groupingBy(A::getId, Reducer.reduce(A::new,
        (fst, snd) -> {
            fst.getList().addAll(snd.getList());
            fst.setId(snd.getId());
            fst.setName(snd.getName());
        }
    ))).values();

最后,如果你已经有其他答案中提到的复制构造函数或合并方法,你可以简化代码甚至更多或使用 Collectors.toMap 方法。

Finally, if you already have the copy constructor or the merge method mentioned in the other answers you can simplify the code even more or use the Collectors.toMap method.

这篇关于使用Java流合并列表中相同对象下的列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆