I have a map like this. Map<long,List<Student>> studentMap
Key is a number 1,2,3,4… Student object is :
public class Student { private long addressNo; private String code; private BigDecimal tax; private String name; private String city; // getter and setters` }
What i want to do is to convert it Map<long,List<StudentInfo>> studentInfoMap
object and group id, addressNo and code fields.I want key are same for both maps.
I can group the map by using these codes but summingDouble is not working for BigDecimal.Also I cannot convert my studentMap to studentInfoMap.:(
studentInfoMap.values().stream() .collect( Collectors.groupingBy(StudentInfo::getCode, Collectors.groupingBy(StudentInfo::getAddressNo, Collectors.summingDouble(StudentInfo::getTax))));
My studentInfo object is :
public class StudentInfo { private long addressNo; private String code; private BigDecimal tax; // getter and setters` }
Advertisement
Answer
For a one-to-one conversion from Student to StudentInfo:
class StudentInfo { public static StudentInfo of(Student student) { StudentInfo si = new StudentInfo(); si.setAddressNo(student.getAddressNo()); si.setCode(student.getCode()); si.setTax(student.getTax()); return si; } }
To convert from one Map
to the other:
Map<Long,List<Student>> studentMap = ... Map<Long,List<StudentInfo>> studentInfoMap = studentMap.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, //same key entry -> entry.getValue().stream() .map(StudentInfo::of) //conversion of Student to StudentInfo .collect(Collectors.toList()) //or simply `.toList()` as of Java 16 ));
Now your grouping….
From the JavaDoc for java.util.stream.Stream<T> public abstract <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector)
:
The following will classify Person objects by city:
Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));The following will classify Person objects by state and city, cascading two Collectors together:
Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
Note how the last example produces a Map
with another Map
as its values.
Now, summingDouble
over StudentInfo::getTax produces a BigDecimal
, not a Map. Replacing with groupingBy
will work to classify Students that have the same amount for getTax
:
Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>> summary = studentInfoMap.values().stream() .flatMap(List::stream) //YOU ALSO NEED THIS .collect( Collectors.groupingBy(StudentInfo::getCode, Collectors.groupingBy(StudentInfo::getAddressNo, Collectors.groupingBy(StudentInfo::getTax))) );
Edit: Retaining the 1,2,3,4 original keys
To retain the original keys you can iterate or stream the original entrySet, which contains both key and value:
Map<Long,Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>>> summaryWithKeys = studentInfoMap.entrySet().stream() //NOTE streaming the entrySet not just values .collect( Collectors.toMap(Map.Entry::getKey, //Original Key with toMap entry -> entry.getValue().stream() //group the value-List members .collect(Collectors.groupingBy(StudentInfo::getCode, Collectors.groupingBy(StudentInfo::getAddressNo, Collectors.groupingBy(StudentInfo::getTax)))) ));
Just as an exercise, if you want a flat map (Map<MyKey,List>
) you need a composite key MyKey
As per my comment, if you are looking to have a single flat Map, you could design a composite key, which would need to implement both equals()
and hashCode()
to contract. For example, this is what Lombok would generate for StudentInfo
(yes, its easier to depend on lombok and use @EqualsAndHashCode
):
public boolean equals(final Object o) { if(o == this) return true; if(!(o instanceof StudentInfo)) return false; final StudentInfo other = (StudentInfo) o; if(!other.canEqual((Object) this)) return false; if(this.getAddressNo() != other.getAddressNo()) return false; final Object this$code = this.getCode(); final Object other$code = other.getCode(); if(this$code == null ? other$code != null : !this$code.equals(other$code)) return false; final Object this$tax = this.getTax(); final Object other$tax = other.getTax(); if(this$tax == null ? other$tax != null : !this$tax.equals(other$tax)) return false; return true; } protected boolean canEqual(final Object other) {return other instanceof StudentInfo;} public int hashCode() { final int PRIME = 59; int result = 1; final long $addressNo = this.getAddressNo(); result = result * PRIME + (int) ($addressNo >>> 32 ^ $addressNo); final Object $code = this.getCode(); result = result * PRIME + ($code == null ? 43 : $code.hashCode()); final Object $tax = this.getTax(); result = result * PRIME + ($tax == null ? 43 : $tax.hashCode()); return result; }
You might then use StudentInfo as the composite key as follows:
Map<Long, List<Student>> studentMap = ... Map<StudentInfo,List<Student>>> summaryMap = studentMap.values().stream() .collect(Collectors.groupingBy(StudentInfo::of)) ));
This means that you now have a nested map referenced by the composite key. Student
s that have exactly the same addressNo, code and tax will be part of the List referenced by each such key.
Edit: Retaining original keys
Similarly, if you wanted to retain the original keys, you could either add them into the composite key, or similar as above:
Map<Long, List<Student>> studentMap = ... Map<Long, Map<StudentInfo,List<Student>>>> summaryMap = studentMap.entrySet().stream() .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.groupingBy(StudentInfo::of))) ));