Group,Sum byType然后使用Java流获得diff

我想使用流来分组,按类型获取总和,然后按类型查找不同的结果。

所以这是我的数据集。

Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD), Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD), Sample(SampleId=3, SampleTypeId=1, SampleQuantity=25, SampleType=ADD), Sample(SampleId=4, SampleTypeId=1, SampleQuantity=5, SampleType=SUBTRACT), Sample(SampleId=5, SampleTypeId=1, SampleQuantity=25, SampleType=SUBTRACT) Sample(SampleId=6, SampleTypeId=2, SampleQuantity=10, SampleType=ADD), Sample(SampleId=7, SampleTypeId=2, SampleQuantity=20, SampleType=ADD), Sample(SampleId=8, SampleTypeId=2, SampleQuantity=30, SampleType=ADD), Sample(SampleId=9, SampleTypeId=2, SampleQuantity=15, SampleType=SUBTRACT), Sample(SampleId=10, SampleTypeId=2, SampleQuantity=35, SampleType=SUBTRACT) 

我目前的做法是这样的:

 Map result = sampleList.stream() .collect( Collectors.groupingBy( Sample::getSampleTypeId, Collectors.summingInt(Sample::getSampleQuantity) ) ); 

我的结果是这样的(SampleTypeId,result):

 {1=75, 2=110} 

我不确定如何从这里继续。 基本上,有一个ADD或SUBTRACT的SampleType。 所以我需要SUM所有TypeId = 1和Type = ADD AND SUM所有typeId = 1和Type = SUBSTRACT然后找到差异。

 1 >> (5+15+25) - (5+25) = 15 2 >> (10+20+30) - (15+35) = 10 

所以预期的输出应该是{1=15, 2=10}

收集器是Java 8中非常强大的function,因为它们中的每一个都可以由不同的块组成,即使这样,也有一种简单的方法可以自己组合收集器。

此解决方案由此类Collector组合解决,下面我将指导您完成解决方案的步骤。

首先,我们从第一个要求开始:结果必须是一个用TypeId键入的字典:

 Map> r = stream.collect( Collectors.groupingBy(Sample::getTypeId) ); 

其次,在每个TypeId列表中,我们需要按Type创建子组。 为此,我们使用Grouping By收集器的扩展版本,这允许我们指定在对元素进行分类并将其分配给组后应该执行的操作。 在以后的情况下,我们需要再次对它们进行分类:

 Map>> r = stream.collect( groupingBy(Sample::getTypeId, groupingBy(Sample::getType)) ); 

现在,一旦两个分类器完成,我们需要在组内的所有元素之间对数量求和。 技术是相同的:我们告诉分类器运行后如何处理组元素。 在这种情况下,将所有金额加起来:

 Map> r = stream.collect( groupingBy(Sample::getTypeId, groupingBy(Sample::getType, Collectors.summingInt(Sample::getQuantity)) ) ); 

接下来,最有趣的部分:我们需要对Map 。 更具体地说,我们需要做map[Add] - map[Sub] 。 这一步最重要的概念是,在返回结果之前,我们希望在完成所有其他收集操作后再执行此操作。 这是一个只能在名为Collector.finisher的流集合阶段发生的操作。 我们可以通过调用Collectors类的方法来组成任意收集器的终结器:

 Map r = stream.collect( groupingBy(Sample::getTypeId, Collectors.collectingAndThen( groupingBy(Sample::getType, summingInt(Sample::getQuantity)), map -> map.getOrDefault(SampleType.ADD, 0) - map.getOrDefault(SampleType.SUBTRACT, 0) ) ) ); 

就这样。 请注意,调用getOrDefault而不是get是很重要的,因为可能没有任何具有某些特定SampleType元素,否则map将返回null ,导致整个事件因NullPointerException而崩溃。

 sampleList.stream() .collect(Collectors.groupingBy(Sample::getSampleTypeId, Collectors.collectingAndThen(Collectors.groupingBy(Sample::getSampleType, Collectors.summingInt(Sample::getSampleQuantity)), map -> (map.getOrDefault(SampleType.ADD, 0) - map.getOrDefault(SampleType.SUBTRACT, 0))))); 

外部groupingBysampleTypeId groupingBy 。 在下一个级别,它涉及使用键SampleType构造映射,并为键的SampleType值求和。 collectingAndThen终结者然后减去ADD和SUBTRACT的值以给出最终结果。

版本2(甚至更简单):

 sampleList.stream() .collect(Collectors.groupingBy(Sample::getSampleTypeId, Collectors.summingInt(sample -> sample.getSampleType() == SampleType.SUBTRACT ? -sample.getSampleQuantity() : sample.getSampleQuantity() ))); 

它不构造先前构造的第二个映射。 它只是检查SampleType是ADD还是SUBTRACT并适当地返回sampleQuantity。

下列:

 1 >> ( 5 + 15 + 25) - ( 5 + 25) = 15 2 >> (10 + 20 + 30) - (15 + 35) = 10 

与此完全相同:

 1 >> 5 + 15 + 25 - 5 - 25 = 15 2 >> 10 + 20 + 30 - 15 - 35 = 10 

因此,您可以利用此function并避免使用SampleType嵌套分组。 相反,只需对下游收集器求和或减去:

 Map result = sampleList.stream() .collect(Collectors.groupingBy( Sample::getSampleTypeId, Collectors.summingInt(sample -> sample.getSampleQuantity() * (SampleType.ADD.equals(sample.getSampleType()) ? 1 : -1)))); 

更好的是,如果你可以在Sample类中创建一个方法:

 public int getCalculatedQuantity() { return sampleQuantity * (SampleType.ADD.equals(sampleType) ? 1 : -1); } 

然后使用如下:

 Map result = sampleList.stream() .collect(Collectors.groupingBy( Sample::getSampleTypeId, Collectors.summingInt(Sample::getCalculatedQuantity))); 

只有map可能有一种更简单的方法:

 Map result = sampleList.stream() .map(sample->{if(sample.getSampleType==SUBTRACT) return new Sample((sample.SampleId, sample.SampleTypeId, sample.SampleQuantity*(-1), sample.SampleType)) return sample; }) .collect( Collectors.groupingBy( Sample::getSampleTypeId, Collectors.summingInt(Sample::getSampleQuantity) ) );