【问题标题】:Convert a List to another List using Stream使用 Stream 将列表转换为另一个列表
【发布时间】:2020-11-19 18:21:40
【问题描述】:

给定,

class Foo {
private Long id;
private String name;
private String category;
private List<String> categories;
// getters & setters
}

我有一个对象列表。

final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");

final List<Foo> li = List.of(f1, f2, f3);

看起来像

{[Foo [id=1, name=a, category=c1, categories=null], Foo [id=1, name=a, category=c2, categories=null]], [Foo [id=2, name=a, category=c1, categories=null]]}

我想把它改成

[Foo [id=1, name=a, category=null, categories=[c1, c2]], Foo [id=2, name=a, category=null, categories=[c1]]]

即将个人category 整理到 categories 列表中。

这是实现我想要的当前代码。

public static void main(final String[] args) {
        final Foo f1 = new Foo(1L, "a", "c1");
        final Foo f2 = new Foo(1L, "a", "c2");
        final Foo f3 = new Foo(2L, "a", "c1");

        final List<Foo> li = List.of(f1, f2, f3);
        li.forEach(e -> System.out.println(e));

        final Map<Long, List<Foo>> collect = li.stream().collect(Collectors.groupingBy(Foo::getId));
        System.out.println(collect);

        final List<Foo> grouped = new ArrayList<>();
        collect.forEach((k, v) -> {
            System.out
                    .println("key=" + k + "val=" + v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));

            final Foo foo = collect.get(k).get(0);
            foo.setCategories(v.stream().map(e1 -> e1.getCategory()).collect(Collectors.toList()));
            foo.setCategory(null);

            grouped.add(foo);
        });

        System.out.println(grouped);
    }

有没有什么方法可以单独使用流和 lambda,而不必分成​​多个步骤?目标是让这段代码更优雅、更易读,并向读者传达意图。

这个问题本质上与Group by and sum objects like in SQL with Java lambdas? 相似,但对我没有帮助,因为这里已经完成了聚合,而这里不是聚合。

【问题讨论】:

  • 严格按照流和 lambda 重新表达计算通常是可能的,但在许多情况下,结果不太优雅、难以阅读且难以理解。想出这样的转变越难,你就越有可能朝着与既定目标相反的方向前进。
  • 仅供参考,当您希望改进工作代码时,codereview.stackexchange.com 更合适。
  • @jaco0646 一个我不知道存在的全新世界(或者不知道是专门建造的)!谢谢!

标签: java java-stream


【解决方案1】:

这可以通过实现一个merge函数来累积类别列表中的类别,然后使用流的reduce操作来完成:

class Foo {
    static Foo merge(Foo accum, Foo other) {
        if (null == accum.categories) {
            accum.categories = new ArrayList<>();
            if (null != accum.category) {
                accum.categories.add(accum.category);
                accum.category = null;
            }
        }
        accum.categories.add(other.category);

        return accum;
    }
}

实施:

static List<Foo> joinedCategoriesReduce(List<Foo> input) {
    return input
            .stream()
            .collect(Collectors.groupingBy(Foo::getId))  // Map<Integer, List<Foo>>
            .values().stream()   // Stream<List<Foo>>
            .map(v -> v.stream() // Stream<Foo>
                    .reduce(new Foo(v.get(0).getId(), v.get(0).getName(), (String)null), Foo::merge)
            )
            .collect(Collectors.toList());
}

测试

final Foo f1 = new Foo(1L, "a", "c1");
final Foo f2 = new Foo(1L, "a", "c2");
final Foo f3 = new Foo(2L, "a", "c1");

final List<Foo> li = List.of(f1, f2, f3);
joinedCategoriesReduce(li).forEach(System.out::println);

输出

Foo(id=1, name=a, category=null, categories=[c1, c2])
Foo(id=2, name=a, category=null, categories=[c1])

另一种选择是提供一个接受类别列表的 Foo 构造函数:

// class Foo
public Foo(Long id, String name, List<String> cats) {
    this.id = id;
    this.name = name;
    this.categories = cats;
}

然后可以通过类别列表轻松地将映射条目重新映射到 Foo 实例:

static List<Foo> joinedCategoriesMap(List<Foo> input) {
    return input
            .stream()
            .collect(Collectors.groupingBy(Foo::getId))
            .values().stream()
            .map(v -> new Foo(
                    v.get(0).getId(),
                    v.get(0).getName(),
                    v.stream().map(Foo::getCategory).collect(Collectors.toList()))
            )
            .collect(Collectors.toList());
}

Online demo

【讨论】:

  • 感谢亚历克斯的回答。它肯定朝着我的目标方向发展。但是,您真的得到了您发布的 o/p 吗?我得到了不同的 o/p Foo [id=1, name=a, category=null, categories=[null, c2]] Foo [id=2, name=a, category=null, categories=[null]] 看起来身份中的 null 搞砸了。
  • 是的,正如我写的那样,你可以查看在线演示的链接:ideone.com/EJEJA6
  • 在最终的代码 sn-p 中,分组后您可以流式传输值而不是条目。我觉得它稍微简单一些。
  • 我的错!由于我身边的一个错误,我得到了null, c2。我采纳了您最后的建议并稍作修改。我没有使用构造函数,而是使用了几种方法来完成工作。我正在探索这个选项,因为我的对象 Foo 可能不是只有 3 个成员的傻瓜。它可能有一大堆成员。这是它的online code。如果问的不是太多,请您看一下,让我知道您是否有任何 f/b?
  • 值得将copyWithoutCategories设为静态并让方法populateCategories返回Foo的当前实例以流畅地链接调用,如StringBuilder,然后可以简化{Foo modified = .. ; return modified;}部分,如显示in the update online
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 2014-07-12
  • 1970-01-01
  • 1970-01-01
  • 2016-05-27
相关资源
最近更新 更多