分组和减少对象列表

2022-09-02 13:10:01

我有一个对象列表,其中包含许多重复的对象和一些需要合并的字段。我想将其简化为仅使用Java 8 Streams的唯一对象列表(我知道如何通过old-skool手段做到这一点,但这是一个实验。

这就是我现在所拥有的。我真的不喜欢这样,因为map构建似乎无关紧要,values()集合是支持映射的视图,你需要将其包装在一个新的集合中才能获得更具体的集合。有没有更好的方法,也许使用更一般的减少操作?ArrayList<>(...)

    @Test
public void reduce() {
    Collection<Foo> foos = Stream.of("foo", "bar", "baz")
                     .flatMap(this::getfoos)
                     .collect(Collectors.toMap(f -> f.name, f -> f, (l, r) -> {
                         l.ids.addAll(r.ids);
                         return l;
                     })).values();

    assertEquals(3, foos.size());
    foos.forEach(f -> assertEquals(10, f.ids.size()));
}

private Stream<Foo> getfoos(String n) {
    return IntStream.range(0,10).mapToObj(i -> new Foo(n, i));
}

public static class Foo {
    private String name;
    private List<Integer> ids = new ArrayList<>();

    public Foo(String n, int i) {
        name = n;
        ids.add(i);
    }
}

答案 1

如果你打破分组并减少步骤,你可以得到一些更干净的东西:

Stream<Foo> input = Stream.of("foo", "bar", "baz").flatMap(this::getfoos);

Map<String, Optional<Foo>> collect = input.collect(Collectors.groupingBy(f -> f.name, Collectors.reducing(Foo::merge)));

Collection<Optional<Foo>> collected = collect.values();

这假设您的类中有一些方便的方法:Foo

public Foo(String n, List<Integer> ids) {
    this.name = n;
    this.ids.addAll(ids);
}

public static Foo merge(Foo src, Foo dest) {
    List<Integer> merged = new ArrayList<>();
    merged.addAll(src.ids);
    merged.addAll(dest.ids);
    return new Foo(src.name, merged);
}

答案 2

正如注释中已经指出的那样,当您想要识别唯一对象时,使用地图是非常自然的。如果您需要做的就是找到唯一的对象,则可以使用该方法。此方法隐藏了涉及映射的事实,但显然它确实在内部使用映射,正如此问题所提示的那样,该问题表明您应该实现一个方法,否则可能无法正确运行。Stream::distincthashCodedistinct

对于该方法,如果不需要合并,则可以在处理完所有输入之前返回一些结果。在您的情况下,除非您可以对问题中未提及的输入做出其他假设,否则您需要在返回任何结果之前完成所有输入的处理。因此,这个答案确实使用了地图。distinct

不过,使用流来处理映射的值并将其转换回 ArrayList 非常简单。我在这个答案中展示了这一点,以及提供了一种方法来避免出现在其他答案之一中的出现。Optional<Foo>

public void reduce() {
    ArrayList<Foo> foos = Stream.of("foo", "bar", "baz").flatMap(this::getfoos)
            .collect(Collectors.collectingAndThen(Collectors.groupingBy(f -> f.name,
            Collectors.reducing(Foo.identity(), Foo::merge)),
            map -> map.values().stream().
                collect(Collectors.toCollection(ArrayList::new))));

    assertEquals(3, foos.size());
    foos.forEach(f -> assertEquals(10, f.ids.size()));
}

private Stream<Foo> getfoos(String n) {
    return IntStream.range(0, 10).mapToObj(i -> new Foo(n, i));
}

public static class Foo {
    private String name;
    private List<Integer> ids = new ArrayList<>();

    private static final Foo BASE_FOO = new Foo("", 0);

    public static Foo identity() {
        return BASE_FOO;
    }

    // use only if side effects to the argument objects are okay
    public static Foo merge(Foo fooOne, Foo fooTwo) {
        if (fooOne == BASE_FOO) {
            return fooTwo;
        } else if (fooTwo == BASE_FOO) {
            return fooOne;
        }
        fooOne.ids.addAll(fooTwo.ids);
        return fooOne;
    }

    public Foo(String n, int i) {
        name = n;
        ids.add(i);
    }
}

推荐