Java 如何组合收集器的结果。groupingBy

Java 如何组合收集器的结果。groupingBy,java,java-stream,Java,Java Stream,我正在玩java反射并学习更多关于Stream.collect的知识 我有一个注释MyTag,它有两个属性(id和typeenum[Normal | Failure])。 另外,我有一个带有MyTag的带注释方法的列表,并且我能够使用收集器根据MyTag注释的id属性对这些方法进行分组。groupingBy: List<Method> ml = getMethodsAnnotatedWith(anClass.getClass(),

我正在玩java反射并学习更多关于Stream.collect的知识

我有一个注释MyTag,它有两个属性(
id
type
enum[Normal | Failure])。 另外,我有一个带有MyTag的带注释方法的列表,并且我能够使用收集器根据MyTag注释的id属性对这些方法进行分组。groupingBy:

List<Method> ml = getMethodsAnnotatedWith(anClass.getClass(),
                                           MyTag.class);
Map<String, List<Method>> map = ml.stream().collect(groupingBy(m -> {
      var ann = m.getDeclaredAnnotation(MyTag.class);
      return ann.anId();
    }, TreeMap::new, toList()));

List ml=getMethodsAnnotatedWith(anClass.getClass(),
MyTag.class);
Map Map=ml.stream().collect(groupingBy(m->{
var ann=m.getDeclaredAnnotation(MyTag.class);
返回ann.anId();
},TreeMap::new,toList());
现在,我需要将结果列表简化为一个对象,该对象只包含两个相同MyTag.id的项,一个包含MyTag.type=Normal,另一个包含MyTag.type=Failure。所以它会产生类似于地图的东西。如果有两个以上的事件,我必须只选择第一个,记录并忽略其余的


如何实现这一点?

下面的示例可以很容易地适应您的代码:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {

    private static final Logger logger = LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        List<Pair<String, String>> ml = Arrays.asList(
                Pair.of("key1", "value1"),
                Pair.of("key1", "value1"),
                Pair.of("key1", "value2"),
                Pair.of("key2", "value1"),
                Pair.of("key2", "value3"));

        Map<String, Pair<String, String>> map = ml.stream().collect(
                Collectors.groupingBy(m -> {
                    return m.getKey();
                }, TreeMap::new, Collectors.toList()))
                .entrySet()
                .stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey, e -> convert(e.getValue())));

        System.out.println(map.values());
    }

    private static Pair<String, String> convert(List<Pair<String, String>> original) {
        long count1 = original.stream().filter(e -> Objects.equals(e.getValue(), "value1")).count();
        long count2 = original.stream().filter(e -> Objects.equals(e.getValue(), "value2")).count();
        if (count1 > 1) {
            logger.warn("More than one occurrence of value1");
        }
        if (count2 > 1) {
            logger.warn("More than one occurrence of value2");
        }
        return Pair.of(count1 > 0 ? "value1" : null,
                count2 > 0 ? "value2" : null);
    }

}

首先,创建自己的
MethodPair
类:

class MethodPair {
  private final Method failure;
  private final Method normal;

  public MethodPair(Method failure, Method normal) {
    this.failure = failure;
    this.normal = normal;
  }

  public Method getFailure() {
    return failure;
  }

  public Method getNormal() {
    return normal;
  }

  public MethodPair combinedWith(MethodPair other) {
    return new MethodPair(
        this.failure == null ? other.failure : this.failure,
        this.normal == null ? other.normal : this.normal)
    );
  }
}
请注意
组合的方法。这将有助于我们将要做的减少

不要使用
toList
,而是使用
reduce
收集器:

Map<String, MethodPair> map = ml.stream().collect(groupingBy(m -> {
  var ann = m.getDeclaredAnnotation(MyTag.class);
  return ann.anId();
}, TreeMap::new,
    Collectors.reducing(new MethodPair(null, null), method -> {
      var type = method.getDeclaredAnnotation(MyTag.class).type();
      if (type == Type.NORMAL) {
        return new MethodPair(null, method);
      } else {
        return new MethodPair(method, null);
      }
    }, MethodPair::combinedWith)
    ));
Map Map=ml.stream(){
var ann=m.getDeclaredAnnotation(MyTag.class);
返回ann.anId();
},TreeMap::新建,
收集器.减少(新方法对(null,null),方法->{
var type=method.getDeclaredAnnotation(MyTag.class).type();
if(type==type.NORMAL){
返回新的MethodPair(null,method);
}否则{
返回新的MethodPair(method,null);
}
},MethodPair::combinedWith)
));
如果您可以分两步完成此操作,我建议您首先创建
映射
,然后将其值映射到新映射。在国际海事组织,这更具可读性:

Map<String, List<Method>> map = ml.stream().collect(groupingBy(m -> {
  var ann = m.getDeclaredAnnotation(MyTag.class);
  return ann.anId();
}, TreeMap::new, toList()));
var result = map.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> {
  Method normal = null;
  Method failure = null;
  for (var m : entry.getValue()) {
    var type = m.getDeclaredAnnotation(MyTag.class).type();
    if (type == Type.NORMAL && normal == null) {
      normal = m;
    } else if (type == Type.FAILURE && failure == null) {
      failure = m;
    }
    if (normal != null && failure != null) {
      break;
    }
  }
  return new MethodPair(failure, normal);
}));
Map Map=ml.stream(){
var ann=m.getDeclaredAnnotation(MyTag.class);
返回ann.anId();
},TreeMap::new,toList());
var result=map.entrySet().stream().collect(Collectors.toMap(entry->entry.getKey(),entry->{
方法normal=null;
方法失败=null;
for(var m:entry.getValue()){
var type=m.getDeclaredAnnotation(MyTag.class).type();
if(type==type.NORMAL&&NORMAL==null){
正常=m;
}else if(type==type.FAILURE&&FAILURE==null){
失效=m;
}
if(正常!=null&&failure!=null){
打破
}
}
返回新的MethodPair(失败,正常);
}));
您可以使用

Map Map=Arrays.stream(anClass.getClass().getMethods())
.filter(m->m.isAnnotationPresent(MyTag.class))
.collect(groupby(m->m.getDeclaredAnnotation(MyTag.class).anId(),
树映射::新的,
toMap(m->m.getDeclaredAnnotation(MyTag.class).aType(),
m->m,(第一,最后)->第一,
()->新的枚举映射(Type.class));
结果将annotations ID属性映射到
映射
,从
类型
(枚举常量
正常
失败
)映射到第一个遇到的具有匹配注释的方法。虽然“first”在迭代反射发现的方法时没有实际意义,因为它不保证任何特定的顺序

()->新的EnumMap(Type.class)
映射工厂不是必需的,当您不指定工厂时,它还可以与默认情况下使用的通用映射一起使用。但是
EnumMap
将以稍微更有效的方式处理只有两个常量要映射的情况,其迭代顺序将匹配enum常量的声明顺序


我认为,
EnumMap
比需要记住哪个方法与“正常”相关,哪个与“失败”相关的
对更好。它也更容易适应两个以上的常数。另外,
EnumMap
是内置的,不需要第三方库。

非常好的解决方案,谢谢。我正在测试它,但有一个问题:每次组合器与标识进行比较时,都会由于MethodPair::combineWith null检查而引发NullPointerException。@Cristiano现在应该得到修复。@Swepper,我能够创建一个收集器实现,在
groupingBy
之后使用,而不是使用
Reduce
。这允许我只更改可变累加器类中的方法值(当为null时),而不是每次都创建一个新对象。它并行工作也很正常。对于大多数情况,@Holger的答案无疑是最灵活的,但我选择你的答案是因为它驱使我去学习我需要的东西。谢谢,谢谢你,霍尔格。非常有趣和灵活的方法。在我的测试中,我可以做一些与您类似的事情,但是使用了一个内部的
groupby
。但它使用了枚举文本。我不知道
EnumMap
。如果我想减少到一个元素,海关收税员是最好的选择?@Cristiano你是说“一个元素”而不是内部地图吗?如果值的简单缩减适合您的任务,则可以使用合并函数将外部的
groupingBy
替换为
toMap
。否则,
groupingBy
使用不同的收集器(无论是内置的还是自定义的)将是正确的选择。与…比较
Map<String, List<Method>> map = ml.stream().collect(groupingBy(m -> {
  var ann = m.getDeclaredAnnotation(MyTag.class);
  return ann.anId();
}, TreeMap::new, toList()));
var result = map.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> {
  Method normal = null;
  Method failure = null;
  for (var m : entry.getValue()) {
    var type = m.getDeclaredAnnotation(MyTag.class).type();
    if (type == Type.NORMAL && normal == null) {
      normal = m;
    } else if (type == Type.FAILURE && failure == null) {
      failure = m;
    }
    if (normal != null && failure != null) {
      break;
    }
  }
  return new MethodPair(failure, normal);
}));