【问题标题】:Dynamic grouping by specific attributes with Collection.stream使用 Collection.stream 按特定属性进行动态分组
【发布时间】:2016-01-13 15:11:24
【问题描述】:

我正在尝试使用 Java 8 Collection-Stream 按多个属性对对象列表进行分组。

这很好用:

public class MyClass
{
   public String title;
   public String type;
   public String module;
   public MyClass(String title, String type, String module)
   {
      this.type = type;
      this.title = title;
      this.module= module;
   }
}

List<MyClass> data = new ArrayList();
data.add(new MyClass("1","A","B"));
data.add(new MyClass("2","A","B"));
data.add(new MyClass("3","A","C"));
data.add(new MyClass("4","B","A"));

Object result = data.stream().collect(Collectors.groupingBy((MyClass m) 
-> m.type, Collectors.groupingBy((MyClass m) -> m.module)));

但我想让它更有活力。 我只想指定一个应该用于 GroupBy 的字符串数组(或列表)。

类似:

Object groupListBy(List data, String[] groupByFieldNames)
{
    //magic code
}

我想打电话:

groupListBy(data, new String[]{"type","module"});

如何使 groupBy-Method 更具动态性,就像我的示例中一样?

【问题讨论】:

    标签: java collections java-8 grouping java-stream


    【解决方案1】:

    使该代码更具动态性的主要问题是您事先不知道要分组的元素数量。在这种情况下,最好按所有元素的List 进行分组。这是有效的,因为如果两个列表的所有元素都相等且顺序相同,则两个列表是相等的。

    在这种情况下,我们将按由每个数据类型和模块组成的列表来分组,而不是先按类型分组,然后按模块分组。

    private static Map<List<String>, List<MyClass>> groupListBy(List<MyClass> data, String[] groupByFieldNames) {
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        List<MethodHandle> handles = 
            Arrays.stream(groupByFieldNames)
                  .map(field -> {
                      try {
                          return lookup.findGetter(MyClass.class, field, String.class);
                      } catch (Exception e) {
                          throw new RuntimeException(e);
                      }
                  }).collect(toList());
        return data.stream().collect(groupingBy(
                d -> handles.stream()
                            .map(handle -> {
                                try {
                                    return (String) handle.invokeExact(d);
                                } catch (Throwable e) {
                                    throw new RuntimeException(e);
                                }
                            }).collect(toList())
            ));
    }
    

    代码的第一部分将字段名称数组转换为ListMethodHandle。对于每个字段,为该字段检索 MethodHandle:这是通过从 MethodHandles.lookup() 获取查找并使用 findGetter 查找给定字段名称的句柄来完成的:

    生成一个方法句柄,授予对非静态字段的读取访问权限。

    其余的代码创建分类器来分组。在数据实例上调用所有句柄以返回 String 值的列表。这个Stream 被收集到一个List 中作为分类器。

    示例代码:

    public static void main(String[] args) {
        List<MyClass> data = new ArrayList<>();
        data.add(new MyClass("1", "A", "B"));
        data.add(new MyClass("2", "A", "B"));
        data.add(new MyClass("3", "A", "C"));
        data.add(new MyClass("4", "B", "A"));
    
        System.out.println(groupListBy(data, new String[] { "type", "module" }));
    }
    

    输出:

    {[B, A]=[4], [A, B]=[1, 2], [A, C]=[3]}
    

    MyClass.toString() 被覆盖以仅返回title 时。

    【讨论】:

      【解决方案2】:

      除了名称列表,您还可以考虑提供一个函数列表(其中一个是强制性的)来对您的元素进行分组。

      这些函数应该将MyClass 的元素映射到一个对象,因此您可以使用Function&lt;MyClass, ?&gt;

      private static Map<List<Object>, List<MyClass>> groupListBy(List<MyClass> data, Function<MyClass, ?> mandatory, Function<MyClass, ?>... others) {
         return data.stream()
                    .collect(groupingBy(cl -> Stream.concat(Stream.of(mandatory), Stream.of(others)).map(f -> f.apply(cl)).collect(toList())));
      }
      

      还有一些调用示例:

      groupListBy(data, m -> m.type); //group only by type
      groupListBy(data, m -> m.type, m -> m.module); //group by type and by module
      

      当然,您可以将此方法设为泛型,以便它返回带有U -&gt; Object 类型函数的Map&lt;List&lt;Object&gt;, List&lt;U&gt;&gt;

      【讨论】:

        猜你喜欢
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多