Java – use collection Stream is dynamically grouped by specific attributes
I tried to group a list of objects by multiple attributes by using java 8 collection stream
This is very effective:
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","B")); data.add(new MyClass("3","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 want to make it more dynamic I just want to specify a string array (or list) that should be used for groupby
It's like:
Object groupListBy(List data,String[] groupByFieldNames) { //magic code }
I'd like to call:
groupListBy(data,new String[]{"type","module"});
How to make the groupby method more dynamic, as in my example?
Solution
The main problem with making your code more dynamic is that you don't know the number of elements to group in advance In this case, it is best to group by a list of all elements This is valid because if all the elements of two lists are the same and in the same order, they are equal
In this case, we will group by a list of each data type and module instead of by 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 converts an array of field names to a list of methodhandles For each field, retrieve the methodhandle for that field: This is done from methodhandles Lookup () gets the handle to find and find the given field name findGetter:
The rest of the code creates classifiers to group Call all handles on the data instance to return a list of string values This stream is collected into a list to be used as a classifier
Example code:
public static void main(String[] args) { List<MyClass> data = new ArrayList<>(); data.add(new MyClass("1","B")); data.add(new MyClass("2","B")); data.add(new MyClass("3","C")); data.add(new MyClass("4","A")); System.out.println(groupListBy(data,new String[] { "type","module" })); }
Output:
{[B,A]=[4],[A,B]=[1,2],C]=[3]}
When MyClass When tostring() is overridden to return only the title