2016-02-03 4 views
2

У меня есть список объектов класса Job, у каждого объекта есть коллекция тегов (сетей), эта коллекция изменчива и не влияет на равенство hashCode и объектов.Java 8 post grouping by

Что мне нужно сделать, чтобы получить список всех уникальных объектов Job и для каждого такого объекта объединить все теги, например, у меня есть список:

[{position: "CTO", dates: "2012-2014", city: "New York", networks: ["foo"]}, {position: "CTO", dates: "2012-2014", city: "New York", networks: ["bar"]}]

Это должно быть уменьшено до [{position: "CTO", dates: "2012-2014", city: "New York", networks: ["foo", "bar"]}]

public class Job { 
    private final String position; 
    private final String dates; 
    private final Integer startYear; 
    private final Integer endYear; 
    private final String city; 
    private Set<NetworkType> networks; 

    public String getPosition() { 
     return position; 
    } 

    public String getDates() { 
     return dates; 
    } 

    public String getCity() { 
     return city; 
    } 

    public Set<NetworkType> getNetworks() { 
     return networks; 
    } 

    public void setNetworks(Set<NetworkType> networks) { 
     this.networks = networks; 
    } 

    public Integer getStartYear() { 
     return startYear; 
    } 

    public Integer getEndYear() { 
     return endYear; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) { 
      return true; 
     } 
     if (!(o instanceof Job)) { 
      return false; 
     } 
     Job job = (Job) o; 
     return Objects.equals(position, job.position) && 
       Objects.equals(dates, job.dates) && 
       Objects.equals(city, job.city); 
    } 

    @Override 
    public int hashCode() { 
     return Objects.hash(position, dates, city); 
    } 
} 

Это фактический код класса работы, и это, как я осуществил эту операцию:

Map<Job, List<Job>> jobsMap = jobs.stream().collect(Collectors.groupingBy(job -> job)); 
    jobsMap.keySet().stream() 
      .peek(job -> jobsMap.get(job).stream().forEach(j -> job.getNetworks().addAll(j.getNetworks()))) 
      .sorted(Comparator.comparing((Job o) -> Objects.firstNonNull(o.getEndYear(), Integer.MAX_VALUE)) 
        .reversed()) 
      .collect(Collectors.toList()); 

Но я чувствую себя очень плохо для этого кода, тем более что я использую внешнюю карту внутри потока, и мне интересно, есть ли способ сделать это в одной цепочке без промежуточных преобразований. Я был бы признателен за любую действительную критику в отношении моей реализации этой функции. Спасибо!

+1

Вам нужно 'groupBy' ваше' задание' 'position', а затем использовать пользовательский коллекционер для объединения вашего' List 'в' Map' в одно задание '. –

+2

Если другие поля, такие как «город», различны, должны ли сети быть объединены с другой работой? – Bohemian

+0

Нет, на самом деле, я группирую объекты по принципу равенства groupingBy (job -> job). Сеть не участвует в равных. – Skeeve

ответ

2

Предполагая, что мы будем объединять все networks в первого возникновения конкретного Job, которые мы находим, что мы можем сделать это в одном (довольно сложный) линии:

import static java.util.stream.Collectors.*; 
import static java.util.function.Function.identity; 

Map<Job, Optional<Job>> collect = jobs.stream() 
    .collect(groupingBy(identity(), reducing((l, r) -> { 
     l.networks().addAll(r.networks()); 
     return l; 
    }))); 

я использовал свободно аксессуаров, потому что я не могу беспокоиться о типе get

So. Как это работает?

Сначала мы streamjobs и вызвать Collectors.groupingBy на Function.identity(), это дает нам Map<Job, List<Job>>.

Но мы не хотим List<Job> - это где Collectors.reducing приходит в этом передается в нисходящем Collector из groupingBy..

Нисходящий Collector отвечает за создание стоимости части Map - в этом случае мы уменьшить все найденные рабочие места в одном Job.

reducing((l, r) -> { 
    l.networks().addAll(r.networks()); 
    return l; 
} 

Так что это занимает два Job элементов и возвращает один. Это операция сгиба, поэтому BiFunction имеет значение return в свою очередь для каждого Job. Все, что мы делаем, это добавить все networks() от нового Job к существующим Job.

Очевидно, что это дает вам Map<String, Optional<Job>>, но рушится, что является простой работой.

Я не вижу способ сделать это непосредственно в List ...


Для того, чтобы обработать Map<Job, Optional<Job>> в List<Job> следующее может быть использовано:

collect.values().stream() 
    .map(Optional::get) 
    .collect(toList); 

Таким образом, вы могли бы потенциально сделать всю партию в одной строке:

List<Job> collect = jobs.stream() 
    .collect(groupingBy(identity(), reducing((l, r) -> { 
     l.networks().addAll(r.networks()); 
     return l; 
    }))) 
    .values().stream() 
    .map(Optional::get) 
    .collect(toList); 

Хотя , читаемость этого вопроса сомнительна.

+0

Большое спасибо! Не могли бы вы обновить свой ответ? Прежде всего, нам нужно использовать весь объект Job для сравнения, а не для позиции, и, во-вторых, мы можем использовать collectAndThen для свертывания опций и Map. Таким образом, это может быть сделано в рамках одной операции: 'jobs.stream() сбор (collectingAndThen (groupingBy (работа -> Работа, collectingAndThen ( \t \t \t \t \t \t \t \t \t \t восстанавливающий ((L, R) -. .> { \t \t \t \t \t \t \t \t \t \t \t l.getNetworks() addAll (r.getNetworks()); \t \t \t \t \t \t \t \t \t \t \t return l; \t \t \t \t \t \t \t \t \t \t}), опционный :: получить)), карта -> новый ArrayList <> (map.values ​​()))) ' – Skeeve

+1

@Skeeve вы каждый сможет разобраться, что что делает одна строка кода? Это уже почти неразборчиво, добавив, что две операции 'collectAndThen' делают его полностью неразборчивым ... Я бы предложил' .values ​​(). Stream(). Map (Необязательный :: get) .collect (toList) 'например. –

+0

Итак, производство нескольких потоков не будет плохой практикой? – Skeeve