【问题标题】:Java 8 Stream Map ReduceJava 8 流图减少
【发布时间】:2015-11-05 11:13:31
【问题描述】:

我对 java 8 流完全陌生,我正在尝试获得以下描述的行为:

class myVO {
    Long id;
    BigDecimal value;
    Date date;
    getter/setter
}

myVO method(Map<Long, myVO> inputMap) {
    return inputMap.stream()
                   .filter(x -> x.getValue().compareTo(BigDecimal.ZERO) > 0)
                   .sorted(); //FIXME
}

我只想获得一个myVO obj,它是同一日期(最低)记录的BigDecimal 值的总和。

例如

xL, 10, 2015/07/07
xL, 15, 2015/07/08
xL, 20, 2015/07/07
xL, 25, 2015/07/09

结果

xL, 30, 2015/07/07

注意id (xL) 不是重要字段。

更新——采用的解决方案(虽然不是单次通过)

if(null != map && !map.isEmpty()) {
        Date closestDate = map.values().stream()
                .filter(t -> t.getDate() != null)
                .map(MyVO::getDate)
                .min(Comparator.naturalOrder()).orElse(null);
        myVO.setDate(closestDate);

        BigDecimal totalValue = map.values().stream()
                .filter(x -> x.getValue() != null && x.getValue().signum() != 0)
                .filter(t -> t.getDate().equals(closestDate))
                .map(MyVO::getValue)
                .reduce(BigDecimal::add).orElse(null);
        myVO.setValue(totalValue != null ? totalValue.setScale(2, BigDecimal.ROUND_HALF_DOWN) : totalValue);
    }

【问题讨论】:

    标签: java dictionary java-8 java-stream reduce


    【解决方案1】:

    考虑到 inputMap 至少有一个条目,可以这样做:

    myVO method(Map<Long, myVO> inputMap) {
        Date minDate = inputMap.values().stream().map(myVO::getDate).min(Comparator.naturalOrder()).get();
    
        BigDecimal sum = inputMap.values().stream().filter(t -> t.getDate().equals(minDate)).map(myVO::getValue).reduce(BigDecimal::add).get();
    
        myVO myVOObj = new myVO();
        myVOObj.setDate(minDate);
        myVOObj.setValue(sum);
        myVOObj.setId(??);
    
        return myVOObj;
    }
    

    【讨论】:

      【解决方案2】:

      我编写了一个自定义收集器来解决这些任务。它在我的StreamEx 库中可用,名为MoreCollectors.maxAll(downstream)。使用它,您可以在一些准备工作后一次性完成任务。

      首先,您的compareTo 方法是错误的。它从不返回 0,实际上它违反了合同(a.compareTo(a) == -1 违反了自反性和反对称性质)。它可以像这样轻松修复:

      @Override
      public int compareTo(myVO o) {
          return o.getDate().compareTo(this.getDate());
      }
      

      接下来,让我们添加一个myVO.merge() 方法,它可以根据您的要求合并两个myVO 对象:

      public myVO merge(myVO other) {
          myVO result = new myVO();
          result.setId(getId());
          result.setDate(getDate());
          result.setValue(getValue().add(other.getValue()));
          return result;
      }
      

      现在可以找到这样的结果:

      Optional<myVO> result = input.stream().filter(x -> x.getValue().signum() > 0)
          .collect(MoreCollectors.maxAll(Collectors.reducing(myVO::merge)));
      

      如果输入列表为空,则生成的Optional 将为空。

      如果你不喜欢依赖第三方库,你可以查看这个收集器的source code,在你的项目中写一些类似的东西。

      【讨论】:

        【解决方案3】:

        如果你仔细想想,减少并没有那么复杂。您必须指定一个归约函数:

        • 如果两个元素的日期不同,则返回日期较小的对象
        • 如果它们匹配,则创建一个包含值总和的结果对象

        与两步法相比,它可能会执行更多的add操作,当流中出现较低的日期时,结果将被丢弃,另一方面,执行的日期比较次数将减半。

        MyVO method(Map<Long, MyVO> inputMap) {
            return inputMap.values().stream()
                .reduce((a,b)->{
                    int cmp=a.getDate().compareTo(b.getDate());
                    if(cmp==0)
                    {
                        MyVO r=new MyVO();
                        r.setDate(a.date);
                        r.setValue(a.value.add(b.value));
                        return r;
                    }
                    return cmp<0? a: b;
                }).orElse(null);
        }
        

        它看起来不简洁的主要原因是它必须创建一个新的MyVO 实例,在匹配日期的情况下保存总和作为缩减函数不得修改值对象。而且您没有指定存在哪些构造函数。如果有适当的构造函数接收Date 和BigDecimal,则该函数几乎可以是单行的。

        请注意,如果只有一个具有最低日期的对象,则此方法将返回原始 MyVO 对象。


        或者,您可以使用可变缩减,始终创建一个新的 MyVO 实例来保存结果,但每个线程只创建一个实例并在缩减期间修改该新实例:

        MyVO method(Map<Long, MyVO> inputMap) {
            BiConsumer<MyVO, MyVO> c=(a,b)->{
                Date date = a.getDate();
                int cmp=date==null? 1: date.compareTo(b.getDate());
                if(cmp==0) a.setValue(a.getValue().add(b.getValue()));
                else if(cmp>0)
                {
                    a.setValue(b.getValue());
                    a.setDate(b.getDate());
                }
            };
            return inputMap.values().stream().collect(()->{
                MyVO r = new MyVO();
                r.setValue(BigDecimal.ZERO);
                return r;
            }, c, c);
        }
        

        这里,如果存在适当的构造函数,Supplier 可以是一行(或者如果保证初始值不是nullBigDecimal.ZERO)...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-02-09
          • 2019-02-17
          • 2014-12-16
          • 2016-09-21
          • 1970-01-01
          • 1970-01-01
          • 2015-02-24
          相关资源
          最近更新 更多