Java 8 Stream Collectors - 用于在多个存储桶中创建包含对象的Map的收集器

时间:2017-01-16 00:41:50

标签: java java-8 java-stream collectors

以下代码有效且可读,但在我看来,我有中间操作,觉得它们不应该是必要的。我已经编写了这个简化版本,因为实际代码是一个更大的过程的一部分。

我有一个Collection Widget,每个都有一个名称和多个类型(由WidgetType枚举的常量表示)。这些多种类型可以作为Stream<WidgetType>获取,但如果有必要,我可以将其作为其他类型返回。 (由于各种原因,非常需要将这些小部件作为Stream<WidgetType>返回,因为这些小部件将在以后的实际代码中使用。)

这些小部件已添加到EnumMap<WidgetType, List<Widget>>,稍后会将其转换为EnumMap<WidgetType, Widget[]>

如果每个Widget只有一个WidgetType,这将是一个微不足道的解决方案,但是,因为任何Widget都可以有一个或多个类型,所以我会用{的语法绊倒我自己{1}}方法(及其重载)。

这里的代码示例再次完全正常,并为我提供了我需要的确切结果。

Collectors.groupingBy()

输出:

class StackOverFlowExample {

    private final Map<WidgetType, Widget[]> widgetMap = new EnumMap<>(WidgetType.class);

    public static void main(String[] args) { new StackOverFlowExample(); }

    StackOverFlowExample() {
        Collection<Widget> widgetList = getWidgetsFromWhereverWidgetsComeFrom();

        {
            final Map<WidgetType, List<Widget>> intermediateMap = new EnumMap<>(WidgetType.class);
            widgetList.forEach(w ->
                    w.getWidgetTypes().forEach(wt -> {
                        intermediateMap.putIfAbsent(wt, new ArrayList<>());
                        intermediateMap.get(wt).add(w);
                    })
            );
            intermediateMap.entrySet().forEach(e -> widgetMap.put(e.getKey(), e.getValue().toArray(new Widget[0])));
        }

        Arrays.stream(WidgetType.values()).forEach(wt -> System.out.println(wt + ": " + Arrays.toString(widgetMap.get(wt))));
    }

    private Collection<Widget> getWidgetsFromWhereverWidgetsComeFrom() {
        return Arrays.asList(
                new Widget("1st", WidgetType.TYPE_A, WidgetType.TYPE_B),
                new Widget("2nd", WidgetType.TYPE_A, WidgetType.TYPE_C),
                new Widget("3rd", WidgetType.TYPE_A, WidgetType.TYPE_D),
                new Widget("4th", WidgetType.TYPE_C, WidgetType.TYPE_D)
        );
    }

}

为了完整起见,这里是TYPE_A: [1st, 2nd, 3rd] TYPE_B: [1st] TYPE_C: [2nd, 4th] TYPE_D: [3rd, 4th] 类和Widget枚举:

WidgetType

欢迎任何关于更好地执行此逻辑的想法。谢谢!

2 个答案:

答案 0 :(得分:4)

恕我直言,关键是将Widget实例转换为Stream<Pair<WidgetType, Widget>>实例。一旦我们有了这个,我们可以flatMap一个小部件流并收集结果流。当然,我们在Java中没有Pair,所以必须改为使用AbstractMap.SimpleEntry

widgets.stream()
       // Convert a stream of widgets to a stream of (type, widget)
       .flatMap(w -> w.getTypes().map(t->new AbstractMap.SimpleEntry<>(t, w)))
       // Grouping by the key, and do additional mapping to get the widget
       .collect(groupingBy(e->e.getKey(),
                mapping(e->e.getValue, 
                        collectingAndThen(toList(), l->l.toArray(new Widget[0])))));

P.S。在这种情况下,IntelliJ的建议并没有通过方法参考来缩短lambda。

答案 1 :(得分:1)

这有点复杂,但它产生相同的输出,不一定是相同的顺序。它使用静态导入java.util.stream.Collectors.*

widgetMap = widgetList.stream()
        .flatMap(w -> w.getWidgetTypes().map(t -> new AbstractMap.SimpleEntry<>(t, w)))
        .collect(groupingBy(Map.Entry::getKey, collectingAndThen(mapping(Map.Entry::getValue, toSet()), s -> s.stream().toArray(Widget[]::new))));

我机器上的输出:

TYPE_A: [1st, 3rd, 2nd]
TYPE_B: [1st]
TYPE_C: [2nd, 4th]
TYPE_D: [3rd, 4th]