Java8 собирает карты

#java #collections #java-8 #functional-programming #java-stream

#java #Коллекции #java-8 #функциональное программирование #java-stream

Вопрос:

У меня есть этот объект:

 public class MenuPriceByDay implements Serializable {

    private BigDecimal avgPrice;
    private BigDecimal minPrice;
    private BigDecimal maxPrice;
    private Date updateDate;

..
}
  

и этот другой:

 public class Statistics {

     double min;
     double max;
     double average;

     public Statistics() {
        super();
     }



    public Statistics(double min, double max, double average) {
        super();
        this.min = min;
        this.max = max;
        this.average = average;
    }



}
  

а также список цен:

 List<MenuPriceByDay> prices = new ArrayList<MenuPriceByDay>();
  

которые я хочу преобразовать в карту:

 Map<LocalDate, Statistics> last30DPerDay =  

        prices
            .stream()
            .collect(Collectors.toMap(MenuPriceByDay::getUpdateDate, p -> new Statistics(   p.getAvgPrice().doubleValue(),
                    p.getMaxPrice().doubleValue(),
                    p.getMinPrice().doubleValue())));
  

но у меня возникла проблема с компиляцией:

 Type mismatch: cannot convert from Map<Date,Object> to 
 Map<LocalDate,Statistics>
  

Комментарии:

1. Что делать, если у вас есть 2 price с одинаковым LocalDate. Какую статистику следует использовать тогда?

Ответ №1:

UpdateDate имеет тип Date , и вы пытаетесь собрать как LocalDate , поэтому вам просто нужно преобразовать Date в LocalDate :

 Map<LocalDate, Statistics> collect = prices.stream()
        .collect(Collectors.toMap(m -> m.getUpdateDate()
        .toInstant()
        .atZone(ZoneId.systemDefault())
        .toLocalDate(),
                 p -> new Statistics(p.getAvgPrice().doubleValue(),
                        p.getMaxPrice().doubleValue(),
                        p.getMinPrice().doubleValue())
        ));
  

Более подробная информация о преобразовании даты в LocalDate или LocalDateTime и обратно

Или вы можете создать метод вместо этого следующим образом :

 public LocalDate convertToLocalDateViaInstant(Date dateToConvert) {
    return dateToConvert.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDate();
}
  

и ваш код может быть :

   Map<LocalDate, Statistics> collect = prices.stream()
            .collect(Collectors.toMap(
                    m -> this.convertToLocalDateViaInstant(m.getUpdateDate()),
                    p -> new Statistics(p.getAvgPrice().doubleValue(),
                            p.getMaxPrice().doubleValue(),
                            p.getMinPrice().doubleValue())
            ));
  

Лучшее решение

лучшее решение — просто изменить updateDate тип на LocalDate :

 private LocalDate updateDate;
  

и вы можете просто использовать свой код

Ответ №2:

Обратите внимание, что при использовании Collectors.toMap вы можете получить IllegalStateException , если у вас есть дубликаты ключей.

Если сопоставленные ключи содержат дубликаты (в соответствии с Object.equals(объект)), при выполнении операции сбора выдается исключение IllegalStateException.

Вместо этого вы можете использовать toMap(keyMapper, valueMapper, mergeFunction) . Также существует решение с groupingBy , и в этом случае вы должны решить, какая статистика должна использоваться при дублировании ключа:

 Map<LocalDate, Optional<Statistics>> map = prices.stream()
            .collect(groupingBy(p -> p.getUpdateDate()
                            .toInstant()
                            .atZone(ZoneId.systemDefault())
                            .toLocalDate(),
                    mapping(p-> new Statistics(
                            p.getMinPrice().doubleValue(),
                            p.getAvgPrice().doubleValue(),
                            p.getMaxPrice().doubleValue()),
                            reducing((s1, s2) -> ???)      // here