2017-01-16 12 views
3

Следующий код работает и доступен для чтения, но мне кажется, что у меня есть промежуточные операции, которые чувствуют, что они не должны быть необходимы. Я написал эту упрощенную версию, поскольку фактический код является частью гораздо более крупного процесса.Java 8 Stream Collectors - Collector для создания карты с объектами в нескольких ведрах

У меня есть Collection из Widget, каждый из которых имеет имя и несколько типов (обозначенных константами перечня WidgetType). Эти множественные типы получаются как Stream<WidgetType>, хотя, если необходимо, я мог бы вернуть их как некоторые другие типы. (По разным причинам, это сильно желательно, что они будут возвращены в качестве Stream<WidgetType> из-за того, как эти виджеты используются в дальнейшем в настоящем коде.)

Эти виджеты добавляются к EnumMap<WidgetType, List<Widget>> который, позже, в переводе на a 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 } 

Любые идеи о лучшем способе выполнения этой логики приветствуются. Благодаря!

ответ

4

IMHO, ключом является преобразование экземпляра Widget в экземпляр Stream<Pair<WidgetType, Widget>>. Как только мы получим это, мы сможем создать поток виджетов и собрать полученный результирующий поток. Конечно, у нас нет Pair на Java, поэтому вместо этого нужно использовать 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 не сокращает лямбда с ссылкой на метод.

+3

Это все еще нуждается в шаге от '' Список к 'Widget []'. Может использовать 'collectAndThen (toList(), l -> l.toArray (новый Widget [0])). –

+1

Спасибо, @JornVernee. Ред. –

+0

'Collectors.mapping()' это часть, которую мне не хватало. Решение 'collectAndThen()' тоже является бонусом. (Я очень смирился с требованием этого дополнительного шага.) Пригвоздил его, Люк. +1 весь день. Бесконечно благодарен! :) – Darkly

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] 

 Смежные вопросы

  • Нет связанных вопросов^_^