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.
The first part of the code transforms the array of field names into a
List
ofMethodHandle
. For each field, aMethodHandle
is retrieved for that field: this is done by obtaining a lookup fromMethodHandles.lookup()
and looking up a handle for the given field name withfindGetter
: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. ThisStream
is collected into aList
to serve as classifier.Sample code:
Output:
when
MyClass.toString()
is overriden to return thetitle
only.Instead of list of names, you could also consider supplying a list of functions (with one mandatory) to group your elements.
These functions should map an element of
MyClass
to an object, so you can useFunction<MyClass, ?>
.And some example of calls:
Of course you can make this method generic so that it returns a
Map<List<Object>, List<U>>
with functions of the typeU -> Object
.