How to group objects in Java 8 - java-8

WalletCreditNoteVO a1 = new WalletCreditNoteVO(1L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.ONE, "GBP");
WalletCreditNoteVO a2 = new WalletCreditNoteVO(1L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.TEN, "GBP");
WalletCreditNoteVO a3 = new WalletCreditNoteVO(2L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.ONE, "GBP");
WalletCreditNoteVO a4 = new WalletCreditNoteVO(2L, 1L, "A", WalletCreditNoteStatus.EXPIRED, null, null, CreditNoteType.CAMPAIGN_VOUCHER, BigDecimal.ONE, BigDecimal.TEN, "GBP");
final List<WalletCreditNoteVO> walletCreditNoteVOs = Lists.newArrayList(a1, a2, a3, a4);
Map<WalletCreditNoteVO, BigDecimal> collect2 = walletCreditNoteVOs.stream().collect(
groupingBy(wr -> new WalletCreditNoteVO(wr.getCreditNoteId(), wr.getWalletCustomerId(), wr.getCreditNoteTitle(),
wr.getWalletCreditNoteStatus(), wr.getCreditNoteStartDate(), wr.getCreditNoteExpiryDate(), wr.getCreditNoteType(), wr.getCreditNoteValue(), wr.getCurrency()),
mapping(WalletCreditNoteVO::getAvailableBalance,
reducing(BigDecimal.ZERO, (sum, elem) -> sum.add(elem)))));
I want to introduce condition for final reducing to be either sum (as written above) or last value in the list of BigDecimal based on the status of getWalletCreditNoteStatus
Can someone please help.
Thanks #xiumeteo . Below is improved solution
Function<WalletCreditNoteVO, WalletCreditNoteVO> function = wr -> new WalletCreditNoteVO(wr.getCreditNoteId(), wr.getWalletCustomerId(), wr.getCreditNoteTitle(),
wr.getWalletCreditNoteStatus(), wr.getCreditNoteStartDate(), wr.getCreditNoteExpiryDate(), wr.getCreditNoteType(), wr.getCreditNoteValue(), wr.getCurrency());
final Map<WalletCreditNoteVO, BigDecimal> collectMap =
walletCreditNoteVOs.stream()
.collect(groupingBy(function, LinkedHashMap::new, Collectors.collectingAndThen(
toList(),
(list) -> {
final List<BigDecimal> availableBalances = list.stream().map(WalletCreditNoteVO::getAvailableBalance).collect(toList());
if (list.stream().allMatch(WalletCreditNoteVO::isStatusExpired)) {
return availableBalances.stream().filter(o -> o != null).reduce((a, b) -> b).orElse(null).abs();
} else {
return availableBalances.stream().filter(o -> o != null).reduce(BigDecimal.ZERO, BigDecimal::add);
}
})));
List<WalletCreditNoteVO> walletCreditNoteVOGrouped = new ArrayList<>();
for(Map.Entry<WalletCreditNoteVO, BigDecimal> entry : collectMap.entrySet()){
WalletCreditNoteVO key = entry.getKey();
key.setAvailableBalance(entry.getValue());
walletCreditNoteVOGrouped.add(key);
}
I now want to remove 'for loop' and stream logic should just give me one list of WalletCreditNoteVO instead of Map of WalletCreditNoteVO as key and BigDecimal as value, with value set directly in the WalletCreditNoteVO
Thanks all again (I can't add code in my comments so adding it here).

So I did a test for your case, I created a dummy class that resembles yours:
public static class Something{
private String name;
private Integer sum;
private boolean checker;
public Something(String name, Integer sum, boolean checker) {
this.name = name;
this.sum = sum;
this.checker = checker;
}
public String getName() {
return name;
}
public boolean isChecker() {
return checker;
}
public Integer getSum() {
return sum;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Something something = (Something) o;
return new EqualsBuilder().append(getName(), something.getName()).append(getSum(), something.getSum()).isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(getName()).append(getSum()).toHashCode();
}
}
And then I did this little test
List<Something> items = Arrays.asList(new Something("name", 10, false), new Something("name", 14, true), new Something("name", 11, false),
new Something("name", 11, false), new Something("noName", 12, false));
final Map<Something, Integer> somethingToSumOrLastElement =
items.stream()
.collect(Collectors.groupingBy(Function.identity(),
Collectors.collectingAndThen(
Collectors.toList(), // first we collect all your related items into a list
(list) -> { //this collector allow us to have a finisher, Function<List<Something>, Object>, let's define it
final List<Integer> integerStream = list.stream().map(Something::getSum).collect(Collectors.toList());
if (list.stream().allMatch(Something::isChecker)) { // we check for the method you want to check
//you have to change this depending on required logic
//for this case if that's true for every element in the list, we do the reduce by summing
return integerStream.stream().reduce(0, (sum, next) -> sum + next);
}
//if not, we just get the last element of that list
return integerStream.stream().reduce(0, (sum, next) -> next);
})));
I think this is ok, but maybe someone has a better idea on how to handle your issue.
Ping me if you need clarification :)

Related

How to do poll values from Priority queue based on a condition

I have map Map<String, PriorityQueue> where the queue is ordered based on the score (reverse). I populated the map from a List where key being data.getGroup and value being Dataitself.
Now my usecase is,
if the size of the map is <=3, I just want to return the Data object so I am just doing a poll top values(Data object) for each key and
if the size of the map is > 3 then I need to get 3 values(1 value/key) from the map based on the score.
For eg:
// output should be just Data(17.0, "five", "D"), Data(4.0, "two", "A"), Data(3.0, "three", "B") though there will be only 4 keys (A,B,C,D)
ArrayList<Data> dataList = new ArrayList<Data>();
dataList.add(new Data(1.0, "one", "A"));
dataList.add(new Data(4.0, "two", "A"));
dataList.add(new Data(3.0, "three", "B"));
dataList.add(new Data(2.0, "four", "C"));
dataList.add(new Data(7.0, "five", "D"));
dataList.add(new Data(17.0, "five", "D"));
// output should be just Data(5.0, "six", "A"), Data(3.14, "two", "B"), Data(3.14, "three", "C") as there will be only 3 keys (A,B,C)
ArrayList<Data> dataList2 = new ArrayList<Data>();
dataList2.add(new Data(3.0, "one", "A"));
dataList2.add(new Data(5.0, "six", "A"));
dataList2.add(new Data(3.14, "two", "B"));
dataList2.add(new Data(3.14, "three", "C"));
I tried the below, but is there a better/smarter (optimized) way to do it in Java?
// n = 3
public List<Data> getTopN(final List<Data> dataList, final int n) {
private static final Comparator< Data > comparator = Comparator.comparing(Data::getScore).reversed();
Map<String, PriorityQueue<Data>> map = Maps.newHashMap();
for (Data data : dataList) {
String key = data.getGroup();
if (key != null) {
if (!map.containsKey(key)) {
map.put(key, new PriorityQueue<>(comparator));
}
map.get(key).add(data);
}
}
if (map.size <= n) {
List<Data> result = new ArrayList<Data>();
for (Map.Entry<String, PriorityQueue<Data>> entrySet: map.entrySet()){
PriorityQueue<Data> priorityQueue = entrySet.getValue();
result.add(priorityQueue.peek());
}
return result;
} else if (map.size > n) {
List<Data> result = new ArrayList<Data>();
for (Map.Entry<String, PriorityQueue<Data>> entrySet: map.entrySet()){
PriorityQueue<Data> priorityQueue = entrySet.getValue();
result.add(priorityQueue.peek());
}
return result.stream()
.sorted(Comparator.comparingDouble(Data::getScore).reversed())
.limit(n)
.collect(Collectors.toList());
}
}
Data Object looks like this:
public class Data {
double score;
String name;
String group;
public void setName(String name) {
this.name = name;
}
public void setGroup(String group) {
this.group = group;
}
public void setScore(double score) {
this.score = score;
}
public String getName() {
return name;
}
public String getGroup() {
return group;
}
public double getScore() {
return score;
}
}
Since your starting point is a List<Data>, there’s not much sense in adding the elements to a Map<String, PriorityQueue<Data>> when all you’re interested in is one value, i.e. the maximum value, per key. In that case, you can simply store the maximum value.
Further, it’s worth considering the differences between the map methods keySet(), values(), and entrySet(). Using the latter is only useful when you’re interested in both, key and value, within the loop’s body. Otherwise, use either keySet() or values() to simplify the operation.
Only when trying to get the top n values from the map, using a PriorityQueue may improve the performance:
private static final Comparator<Data> BY_SCORE = Comparator.comparing(Data::getScore);
private static final BinaryOperator<Data> MAX = BinaryOperator.maxBy(BY_SCORE);
public List<Data> getTopN(List<Data> dataList, int n) {
Map<String, Data> map = new HashMap<>();
for(Data data: dataList) {
String key = data.getGroup();
if(key != null) map.merge(key, data, MAX);
}
if(map.size() <= n) {
return new ArrayList<>(map.values());
}
else {
PriorityQueue<Data> top = new PriorityQueue<>(n, BY_SCORE);
for(Data d: map.values()) {
top.add(d);
if(top.size() > n) top.remove();
}
return new ArrayList<>(top);
}
}
Note that the BinaryOperator.maxBy(…) is using the ascending order as basis and also the priority queue now needs the ascending order, as we’re removing the smallest elements such that the top n remain in the queue for the result. Therefore, reversed() has been removed from the Comparator here.
Using a priority queue provides a benefit if n is small, especially in comparison to the map’s size. If n is rather large or expected to be close to the map’s size, it is likely more efficient to use
List<Data> top = new ArrayList<>(map.values());
top.sort(BY_SCORE.reversed());
top.subList(n, top.size()).clear();
return top;
which sorts all of the map’s values in descending order and removes the excess elements. This can be combined with the code handling the map.size() <= n scenario:
public List<Data> getTopN(List<Data> dataList, int n) {
Map<String, Data> map = new HashMap<>();
for(Data data: dataList) {
String key = data.getGroup();
if(key != null) map.merge(key, data, MAX);
}
List<Data> top = new ArrayList<>(map.values());
if(top.size() > n) {
top.sort(BY_SCORE.reversed());
top.subList(n, top.size()).clear();
}
return top;
}

Java Map: group by key's attribute and max over value

I have an instance of Map<Reference, Double> the challenge is that the key objects may contain a reference to the same object, I need to return a map of the same type of the "input" but grouped by the attribute key and by retaining the max value.
I tried by using groupingBy and maxBy but I'm stuck.
private void run () {
Map<Reference, Double> vote = new HashMap<>();
Student s1 = new Student(12L);
vote.put(new Reference(s1), 66.5);
vote.put(new Reference(s1), 71.71);
Student s2 = new Student(44L);
vote.put(new Reference(s2), 59.75);
vote.put(new Reference(s2), 64.00);
// I need to have a Collection of Reference objs related to the max value of the "vote" map
Collection<Reference> maxVote = vote.entrySet().stream().collect(groupingBy(Map.Entry.<Reference, Double>comparingByKey(new Comparator<Reference>() {
#Override
public int compare(Reference r1, Reference r2) {
return r1.getObjId().compareTo(r2.getObjId());
}
}), maxBy(Comparator.comparingDouble(Map.Entry::getValue))));
}
class Reference {
private final Student student;
public Reference(Student s) {
this.student = s;
}
public Long getObjId() {
return this.student.getId();
}
}
class Student {
private final Long id;
public Student (Long id) {
this.id = id;
}
public Long getId() {
return id;
}
}
I have an error in the maxBy argument: Comparator.comparingDouble(Map.Entry::getValue) and I don't know how to fix it. Is there a way to achieve the expected result?
You can use Collectors.toMap to get the collection of Map.Entry<Reference, Double>
Collection<Map.Entry<Reference, Double>> result = vote.entrySet().stream()
.collect(Collectors.toMap( a -> a.getKey().getObjId(), Function.identity(),
BinaryOperator.maxBy(Comparator.comparingDouble(Map.Entry::getValue)))).values();
then stream over again to get List<Reference>
List<Reference> result = vote.entrySet().stream()
.collect(Collectors.toMap(a -> a.getKey().getObjId(), Function.identity(),
BinaryOperator.maxBy(Comparator.comparingDouble(Map.Entry::getValue))))
.values().stream().map(e -> e.getKey()).collect(Collectors.toList());
Using your approach of groupingBy and maxBy:
Comparator<Entry<Reference, Double>> c = Comparator.comparing(e -> e.getValue());
Map<Object, Optional<Entry<Reference, Double>>> map =
vote.entrySet().stream()
.collect(
Collectors.groupingBy
(
e -> ((Reference) e.getKey()).getObjId(),
Collectors.maxBy(c)));
// iterate to get the result (or invoke another stream)
for (Entry<Object, Optional<Entry<Reference, Double>>> obj : map.entrySet()) {
System.out.println("Student Id:" + obj.getKey() + ", " + "Max Vote:" + obj.getValue().get().getValue());
}
Output (For input in your question):
Student Id:12, Max Vote:71.71
Student Id:44, Max Vote:64.0

Observable that combines behavior of groupby with combinelatest and exhibits "outer join" behavior

I'm trying to use reactive paradigm to create an observable that acts like a combination of "group by" and combinelatest. I have two source observables that have a shared joining key, like in the following two data structures.
class Foo
{
string Key;
string Funky;
}
class Bar
{
string Key;
string Town;
}
What I want is an observable that yields me the latest combination of these two joined on InstrumentID. The end result should look something like:
class Target
{
string Key;
string Funky;
string Town;
}
and exhibits an "outer join" like behavior, meaning the first sequence to produce a new "key" will yield a Target class with the other side being null, and then once the other side also produces the same joining key, the latest from both sides is yielded whenever there's a new value in either sequence for the given key.
Let's say your foo$ stream emits values of type Foo, and bar$ stream emits values of Bar.
Here is how you can combine them:
combineLatest([
foo$,
bar$
// use startWith(null) to ensure combineLatest will emit as soon as foo$ emits, not waiting for bar$ to emit its first value
.pipe(startWith(null))
]).pipe(
map(([foo, bar]) => ({
// always keep all properties from foo
...foo,
// only add properties from bar if it has the matching Key
...(bar && bar.Key === foo.Key ? bar : null)
}))
)
This may not be "cosher" by some standards but works for what I need it to do. Posting for anyone looking for same functionality (.NET Version of RX)
public static class Extensions
{
public static IObservable<TResult> CombineLatestGrouped<TFirst,TSecond,TKey, TResult>(
this IObservable<TFirst> first,
IObservable<TSecond> second,
Func<TFirst, TKey> firstKeySelector,
Func<TSecond, TKey> secondKeySelector,
Func<TKey,TFirst,TSecond,TResult> resultSelector)
{
var dic = new ConcurrentDictionary<TKey,Tuple<TFirst,TSecond>>();
return Observable.Create<TResult>(obs =>
{
var d1 = first
.Select(x =>
{
var key = firstKeySelector(x);
var tuple = dic.AddOrUpdate(
key,
addValueFactory: key => Tuple.Create(x, default(TSecond)),
updateValueFactory: (key, existing) => Tuple.Create(x, existing.Item2));
return resultSelector(key, tuple.Item1, tuple.Item2);
})
.Subscribe(obs);
var d2 = second
.Select(x =>
{
var key = secondKeySelector(x);
var tuple = dic.AddOrUpdate(
key,
addValueFactory: key => Tuple.Create(default(TFirst), x),
updateValueFactory: (key, existing) => Tuple.Create(existing.Item1, x));
return resultSelector(key, tuple.Item1, tuple.Item2);
})
.Subscribe(obs);
return new CompositeDisposable(d1, d2);
});
}
}
As I see it, you would like following:
a new "key" will yield a Target class with the other side being null
When left or right side emmits a NEW key (prev: null or different)
, and then once the other side also produces the same joining key,
precondition: a stream emitted a value -- other stream now emits a value and the key for left and right eq
the latest from both sides is yielded whenever there's a new value in either sequence for the given key.
emit full target (composed of left, right) on each left,right emit, when a value of left,right changes distinctly
RxJava2 solution for my assumption:
#Test
void test2() {
PublishSubject<Foo> foo$ = PublishSubject.create();
PublishSubject<Bar> bar$ = PublishSubject.create();
Observable<Target> target$ = Observable.merge(Arrays.asList(foo$, bar$))
// filter invalid values
.filter(hasId -> hasId.key() != null)
.scan(Target.NULL, (prev, change) -> {
// when prev. target and current value#key are eq -> emit composed value
if (change.key().equals(prev.key)) {
return composedTarget(prev, change);
} else if (change instanceof Foo) {
return Target.fromFoo((Foo) change);
} else if (change instanceof Bar) {
return Target.fromBar((Bar) change);
}
return prev;
}).filter(target -> target != Target.NULL)
.distinctUntilChanged();
TestObserver<Target> test = target$.test();
// emit
foo$.onNext(new Foo("123", "f1"));
// emit
bar$.onNext(new Bar("123", "f2"));
// emit
bar$.onNext(new Bar("123", "f3"));
// skipped
foo$.onNext(new Foo("123", "f1"));
// emit
foo$.onNext(new Foo("123", "f5"));
// emit
foo$.onNext(new Foo("key", "value"));
// emit
foo$.onNext(new Foo("key2", "value2"));
// emit
bar$.onNext(new Bar("bar2", "Berlin"));
// emit
foo$.onNext(new Foo("foo2", "Funkeey"));
test.assertValues(
new Target("123", "f1", null),
new Target("123", "f1", "f2"),
new Target("123", "f1", "f3"),
new Target("123", "f5", "f3"),
new Target("key", "value", null),
new Target("key2", "value2", null),
new Target("bar2", null, "Berlin"),
new Target("foo2", "Funkeey", null)
);
}
private Target composedTarget(Target prev, HasId change) {
if (change instanceof Foo) {
Foo foo = (Foo) change;
return new Target(prev.key, foo.funky, prev.town);
}
if (change instanceof Bar) {
Bar bar = (Bar) change;
return new Target(prev.key, prev.funky, bar.town);
}
return prev;
}
Domain-Classes
interface HasId {
String key();
}
static final class Foo implements HasId {
final String key;
final String funky;
Foo(String key, String funky) {
this.key = key;
this.funky = funky;
}
#Override
public String key() {
return key;
}
}
static final class Bar implements HasId {
String key;
String town;
Bar(String key, String town) {
this.key = key;
this.town = town;
}
#Override
public String key() {
return key;
}
}
static final class Target {
private static final Target NULL = new Target(null, null, null);
final String key;
final String funky;
final String town;
Target(String key, String funky, String town) {
this.key = key;
this.funky = funky;
this.town = town;
}
static Target fromFoo(Foo foo) {
return new Target(foo.key, foo.funky, null);
}
static Target fromBar(Bar bar) {
return new Target(bar.key, null, bar.town);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Target target = (Target) o;
return key.equals(target.key) &&
Objects.equals(funky, target.funky) &&
Objects.equals(town, target.town);
}
#Override
public int hashCode() {
return Objects.hash(key, funky, town);
}
#Override
public String toString() {
return "Target{" +
"key='" + key + '\'' +
", funky='" + funky + '\'' +
", town='" + town + '\'' +
'}';
}
}
Please correct my assumptions, if I am mistaken. The solution could be implemented way better in C# with pattern-matching. Actually if C# has union types like F#, that would be best.

Java Streaming: get max if no duplicates

I'm trying to write a function that takes in a Map and returns an Entry. If the entry with the max Integer value is unique, it should return that entry. However, if there are duplicate entries with the same max value, it should return a new Entry with a key of "MULTIPLE" and a value of 0. It's easy enough for me to get the max value ignoring duplicates:
public static Entry<String,Integer> getMax(Map<String,Integer> map1) {
return map1.entrySet().stream()
.max((a,b) -> a.getValue().compareTo(b.getValue()))
.get();
}
But in order for me to do what I said initially, I could only find a solution where I had to create an initial stream to do a boolean check if there were multiple max values and then do another stream if not to get the value. I'd like to find a solution where I can do both tasks with only one stream.
Here's my little test case:
#Test
public void test1() {
Map<String,Integer> map1 = new HashMap<>();
map1.put("A", 100);
map1.put("B", 100);
map1.put("C", 100);
map1.put("D", 105);
Assert.assertEquals("D", getMax(map1).getKey());
Map<String,Integer> map2 = new HashMap<>();
map2.put("A", 100);
map2.put("B", 105);
map2.put("C", 100);
map2.put("D", 105);
Assert.assertEquals("MULTIPLE", getMax(map2).getKey());
This is a simple case of reduction, and you don't need any external libraries.
Map.Entry<String, Integer> max(Map<String, Integer> map) {
return map.entrySet().stream()
.reduce((e1, e2) -> {
if (e1.getValue() == e2.getValue()) {
return new SimpleImmutableEntry<>("MULTIPLE", 0);
} else {
return Collections.max(asList(e1, e2), comparingInt(Map.Entry::getValue));
}
})
.orElse(new SimpleImmutableEntry<>("NOT_FOUND", 0));
}
Here is the solution by StreamEx
public Entry<String, Integer> getMax(Map<String, Integer> map) {
return StreamEx.of(map.entrySet()).collect(collectingAndThen(MoreCollectors.maxAll(Map.Entry.comparingByValue()),
l -> l.size() == 1 ? l.get(0) : new AbstractMap.SimpleImmutableEntry<>("MULTIPLE", 0)));
}
Another solution is iterating the map twice with potential better performance:
public Entry<String, Integer> getMax(Map<String, Integer> map) {
int max = map.entrySet().stream().mapToInt(e -> e.getValue()).max().getAsInt();
return StreamEx.of(map.entrySet()).filter(e -> e.getValue().intValue() == max).limit(2)
.toListAndThen(l -> l.size() == 1 ? l.get(0) : new AbstractMap.SimpleImmutableEntry<>("MULTIPLE", 0));
}

Java 8 is not maintaining the order while grouping

I m using Java 8 for grouping by data. But results obtained are not in order formed.
Map<GroupingKey, List<Object>> groupedResult = null;
if (!CollectionUtils.isEmpty(groupByColumns)) {
Map<String, Object> mapArr[] = new LinkedHashMap[mapList.size()];
if (!CollectionUtils.isEmpty(mapList)) {
int count = 0;
for (LinkedHashMap<String, Object> map : mapList) {
mapArr[count++] = map;
}
}
Stream<Map<String, Object>> people = Stream.of(mapArr);
groupedResult = people
.collect(Collectors.groupingBy(p -> new GroupingKey(p, groupByColumns), Collectors.mapping((Map<String, Object> p) -> p, toList())));
public static class GroupingKey
public GroupingKey(Map<String, Object> map, List<String> cols) {
keys = new ArrayList<>();
for (String col : cols) {
keys.add(map.get(col));
}
}
// Add appropriate isEqual() ... you IDE should generate this
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GroupingKey other = (GroupingKey) obj;
if (!Objects.equals(this.keys, other.keys)) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 37 * hash + Objects.hashCode(this.keys);
return hash;
}
#Override
public String toString() {
return keys + "";
}
public ArrayList<Object> getKeys() {
return keys;
}
public void setKeys(ArrayList<Object> keys) {
this.keys = keys;
}
}
Here i am using my class groupingKey by which i m dynamically passing from ux. How can get this groupByColumns in sorted form?
Not maintaining the order is a property of the Map that stores the result. If you need a specific Map behavior, you need to request a particular Map implementation. E.g. LinkedHashMap maintains the insertion order:
groupedResult = people.collect(Collectors.groupingBy(
p -> new GroupingKey(p, groupByColumns),
LinkedHashMap::new,
Collectors.mapping((Map<String, Object> p) -> p, toList())));
By the way, there is no reason to copy the contents of mapList into an array before creating the Stream. You may simply call mapList.stream() to get an appropriate Stream.
Further, Collectors.mapping((Map<String, Object> p) -> p, toList()) is obsolete. p->p is an identity mapping, so there’s no reason to request mapping at all:
groupedResult = mapList.stream().collect(Collectors.groupingBy(
p -> new GroupingKey(p, groupByColumns), LinkedHashMap::new, toList()));
But even the GroupingKey is obsolete. It basically wraps a List of values, so you could just use a List as key in the first place. Lists implement hashCode and equals appropriately (but you must not modify these key Lists afterwards).
Map<List<Object>, List<Object>> groupedResult=
mapList.stream().collect(Collectors.groupingBy(
p -> groupByColumns.stream().map(p::get).collect(toList()),
LinkedHashMap::new, toList()));
Based on #Holger's great answer. I post this to help those who want to keep the order after grouping as well as changing the mapping.
Let's simplify and suppose we have a list of persons (int age, String name, String adresss...etc) and we want the names grouped by age while keeping ages in order:
final LinkedHashMap<Integer, List<String> map = myList
.stream()
.sorted(Comparator.comparing(p -> p.getAge())) //sort list by ages
.collect(Collectors.groupingBy(p -> p.getAge()),
LinkedHashMap::new, //keeps the order
Collectors.mapping(p -> p.getName(), //map name
Collectors.toList())));

Resources