使用Collection.stream按特定属性进行动态分组
我试图通过使用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 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的String-Array(或List)。
就像是:
Object groupListBy(List data, String[] groupByFieldNames) { //magic code }
我想打电话给:
groupListBy(data, new String[]{"type","module"});
如何使groupBy-Method更具动态性,就像在我的例子中一样?
使代码更具动态性的主要问题是,您事先并不知道要分组的元素数量。 在这种情况下,最好按所有元素的List
进行分组。 这是有效的,因为如果所有元素的所有元素相同且顺序相同,则两个列表相等。
在这种情况下,我们将按照包含每种数据类型和模块的列表进行分组,而不是按类型和模块进行分组。
private static Map, List> groupListBy(List data, String[] groupByFieldNames) { final MethodHandles.Lookup lookup = MethodHandles.lookup(); List 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()) )); }
代码的第一部分将字段名称数组转换为MethodHandle
的List
。 对于每个字段,将MethodHandle
字段检索MethodHandle
:这是通过从MethodHandles.lookup()
获取查找并使用MethodHandles.lookup()
查找给定字段名称的句柄来findGetter
:
生成一个方法句柄,提供对非静态字段的读访问权限。
其余代码创建分类器来分组。 在数据实例上调用所有句柄以返回String
值列表。 此Stream
被收集到List
以用作分类器。
示例代码:
public static void main(String[] args) { List 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
。
您也可以考虑提供一个function列表(有一个必填项)来对元素进行分组,而不是名称列表。
这些函数应该将MyClass
的元素映射到一个对象,因此您可以使用Function
。
private static Map, List> groupListBy(List data, Function mandatory, Function... 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
当然,你可以使这个方法通用,以便它返回一个Map
,其函数类型为, List>
U -> Object
。