组、收集器、映射(Int 到字符串)、映射(映射到对象)

Group, Collectors, Map (Int to String), Map (Map to Object)

这是我之前在 的问题的延续。

按照建议,我应该 post 作为一个单独的线程而不是更新原始线程。

所以对于我之前的一组问题,我已经做到了,现在,随着继续。

背景:

我有以下数据集

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)

我目前正在使用这个:

sampleList.stream()
    .collect(Collectors.groupingBy(Sample::getTypeId,
        Collectors.summingInt(
            sample -> SampleType.ADD.equalsIgnoreCase(sample.getSampleType())
                ? sample.getSampleQuantity() :
                -sample.getSampleQuantity()
            )));

还有这个

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))));

作为接受的答案,以获得所需的输出以分组 Map<Long, Integer>:

{1=15, 2=10}

有了这个,我想知道是否可以将其扩展为更多内容。

首先,我怎么能把它 return 作为 Map<String, Integer> 而不是原来的 Map<Long, Integer>。基本上,对于 SampleTypeId; 1代表你好,2代表世界。

所以我需要一个 .map(或者可能是其他函数)通过​​调用函数 say convertType(sampleTypeId)? 将数据从 1 转换为 HELLO,将 2 转换为 WORLD。所以预期的输出将是 {"HELLO"=15, "WORLD"=10}。那正确吗?我应该如何编辑当前建议的解决方案?

最后,我想知道是否也可以 return 将它变成对象而不是 Map。所以假设我有一个对象; SummaryResult with (String) name and (int) result。所以它 return 是 List<SummaryResult> 而不是原来的 Map<Long, Integer>。如何使用 .map(或其他)功能来执行此操作?或者还有其他方法吗?预期的输出将是沿着这条线的东西。

SummaryResult(name="hello", result=15), 
SummaryResult(name="world", result=10), 

非常感谢 @M 之前给出的步骤中的解释。普罗霍罗夫

更新:

更新到

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

private String convertType(int id) {
    return (id == 1) ? "HELLO" : "WORLD";
}

你确实可以使用 map() 函数

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))))
        .entrySet()            
        .stream()            
        .map(entry->new SummaryResult(entry.getKey()),entry.getValue())
        .collect(Collectors.toList());

对于第一部分,考虑到你有方法

String convertType(int typeId)

你只需要从这个改变第一个分类器

groupingBy(SampleType::getTypeId)

至此

groupingBy(sample -> convertType(sample.getTypeId()))

其他一切保持不变。

后一种类型有点棘手,从技术上讲,它根本无法从 stream-related 解决方案中获益。

你需要的是这个:

public List<SummaryResult> toSummaryResultList(Map<String, Integer> resultMap) {
  List<SummaryResult> list = new ArrayList<>(resultMap.size());
  for (Map.Entry<String, Integer> entry : resultMap.entrySet()) {
    String name = entry.getKey();
    Integer value = entry.getValue();

    // replace below with construction method you actually have
    list.add(SummaryResult.withName(name).andResult(value));
  }
  return list;
}

您可以将其用作收集器组合的一部分,您的整个收集器将被包装到一个 collectingAndThen 调用中:

collectingAndThen(
  groupingBy(sample -> convertType(sample.getTypeId()),
     collectingAndThen(
       groupingBy(Sample::getSampleType,
         summingInt(Sample::getSampleQuantity)),
         map -> map.getOrDefault(SampleType.ADD, 0)
                - map.getOrDefault(SampleType.SUBTRACT, 0))),
  result -> toSummaryResultList(result))

但是,如您所见,包装的是整个收集器,因此在我看来,上面的版本比下面的更简单、更容易理解(至少对我而言)的版本没有真正的好处使用一个中间变量,但不是一大堆代码:

// do the whole collecting thing like before
Map<String, Integer> map = sampleList.stream()
    .collect(Collectors.groupingBy(sample -> convertType(sample.getTypeId()),
            Collectors.collectingAndThen(
                Collectors.groupingBy(Sample::getSampleType,
                    Collectors.summingInt(Sample::getSampleQuantity)),
                        map -> map.getOrDefault(SampleType.ADD, 0)
                                - map.getOrDefault(SampleType.SUBTRACT, 0))));

// return the "beautified" result
return toSummaryResultList(map);

上面要考虑的另一点是:convertType 方法将被调用的次数与 sampleList 中的元素一样多,因此如果 convertType 调用是 "heavy" (例如,使用数据库或IO),那么最好将其称为toSummaryResultList转换的一部分,而不是作为流元素分类器。在这种情况下,您仍将从类型为 Map<Integer, Integer> 的地图中收集数据,并在循环内使用 convertType。考虑到这一点,我不会添加任何代码,因为我认为此更改微不足道。

ToIntFunction<Sample> signedQuantityMapper= sample -> sample.getQuantity() 
    * (sample.getType() == Type.ADD ? 1 : -1);

Function<Sample, String> keyMapper = s -> Integer.toString(s.getTypeId());

Map<String, Integer> result = sampleList.stream().collect(
    Collectors.groupingBy(
        keyMapper,
        Collectors.summingInt(signedQuantityMapper)));