有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java 8流收集器用于创建包含多个存储桶中的对象的映射

下面的代码可以工作并且可读,但在我看来,我有一些中间操作,感觉它们不必要。我编写了这个简化版本,因为实际代码是更大流程的一部分

我得到了CollectionWidget,每个都有一个名称和多个类型(由WidgetType枚举的常量表示)。这些多个类型可以作为Stream<WidgetType>获取,但如果需要,我可以将它们作为其他类型返回。(由于各种原因,强烈希望这些小部件以Stream<WidgetType>的形式返回,因为这些小部件在实际代码中稍后的使用方式。)

这些小部件被添加到一个EnumMap<WidgetType, List<Widget>>中,这个EnumMap<WidgetType, Widget[]>稍后被翻译成一个EnumMap<WidgetType, Widget[]>

如果每个Widget只有一个WidgetType,这将是一个微不足道的解决方案,但是,由于任何小部件都可能有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枚举:

class Widget {

    private final String       name;
    private final WidgetType[] widgetTypes;

    Widget(String n, WidgetType ... wt) { name = n; widgetTypes = wt; }

    public String getName() { return name; }
    public Stream<WidgetType> getWidgetTypes() { return Arrays.stream(widgetTypes).distinct(); }

    @Override public String toString() { return name; }
}

enum WidgetType { TYPE_A, TYPE_B, TYPE_C, TYPE_D }

任何关于更好地执行这种逻辑的想法都是受欢迎的。谢谢


共 (2) 个答案

  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]
    
  2. # 2 楼答案

    关键是将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])))));
    

    另外,在这种情况下,IntelliJ的建议不会使用方法参考缩短λ