使用Collection.stream按特定属性动态分组 [英] Dynamic grouping by specific attributes with Collection.stream

查看:1089
本文介绍了使用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的字符串数组(或List)。



像:

 对象groupListBy(列表数据,String [] groupByFieldNames)
{
//魔术代码
}

,我想呼叫:

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

如何使groupBy-Method更具动态性,就像我的例子?

解决方案

使代码更加动态的主要问题是,你不知道提前有多少个元素将被分组。在这种情况下,最好按所有元素的 List 分组。这是因为两个列表是相等的,如果它们的所有元素是相等的和在相同的顺序。



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

  private static Map< List< String>,List< MyClass> ;> groupListBy(List< MyClass> data,String [] groupByFieldNames){
final MethodHandles.Lookup lookup = MethodHandles.lookup();
List< MethodHandle> handle =
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);
}
}
));
}

代码的第一部分将字段名称数组转换为列出 MethodHandle 。对于每个字段,检索该字段的 MethodHandle :这是通过从 MethodHandles.lookup() 并查找给定字段名称为 findGetter

p>生成一个方法句柄,给予对非静态字段的读取访问。


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



示例代码:

  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()
被覆盖以返回<$ c $>时,c $ c>



< c> title


I am trying to group a list of objects by mulitple attributes, by using Java 8 Collection-Stream.

This works pretty well:

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

But I would like to make it a little more dynamic. I just want to specify an String-Array (or List) which should be used to GroupBy.

Something like:

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

and I want to call:

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

How can I make the groupBy-Method more dynamic, like in my example?

解决方案

The main problem with making that code more dynamic is that you don't know in advance how many elements there will be to group by. In such a case, it is best to group by the List of all the elements. This works because two lists are equal if all of their elements are equal and in the same order.

In this case, instead of grouping by the type and then the module, we will group by the list consisting of each data type and module.

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

The first part of the code transforms the array of field names into a List of MethodHandle. For each field, a MethodHandle is retrieved for that field: this is done by obtaining a lookup from MethodHandles.lookup() and looking up a handle for the given field name with findGetter:

Produces a method handle giving read access to a non-static field.

The rest of the code creates the classifier to group by from. All the handles are invoked on the data instance to return the list of String value. This Stream is collected into a List to serve as classifier.

Sample code:

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

Output:

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

when MyClass.toString() is overriden to return the title only.

这篇关于使用Collection.stream按特定属性动态分组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆