I have a class Student:
public class Student {
private String name;
private int age;
private String city;
private double salary;
private double incentive;
// getters, all-args constructor, etc.
}
And I have a list of Student instances called students.
I want to create a new list which will contain the Students grouped by their name, age and city. The salary and incentive of the students having these attributes identical should be summed up.
Example:
Input:
Student("Raj",10,"Pune",10000,100)
Student("Raj",10,"Pune",20000,200)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",10000,100)
Student("Ram",30,"Pune",30000,300)
Student("Seema",10,"Pune",10000,100)
Output:
Student("Raj",10,"Pune",30000,300)
Student("Raj",20,"Pune",10000,100)
Student("Ram",30,"Pune",40000,400)
Student("Seema",10,"Pune",10000,100)
My attempt:
List<Student> students = // initializing the list
List<Student> res = new ArrayList<>(students.stream()
.collect(Collectors.toMap(
ec -> new AbstractMap.SimpleEntry<>(ec.getName(),ec.getAge(),ec.getCity()),
Function.identity(),
(a, b) -> new Student(
a.getName(), a.getAge(), a.getCity(), a.getSalary().add(b.getSalary()),a.getIncentive().add(b.getIncentive())
)
))
.values())));
Which produces a compilation error:
Compile error- Cannot resolve constructor 'SimpleEntry(String, int, String)' and Cannot resolve method 'add(double)
I've also tried some other options, but without success. How can I achieve that?
To obtain the total
salaryandincentivefor students having the samename,ageandcityyou can group the data using aMap. So you were thinking in the right direction.In order to achieve that you would need some object, that would hold references
name,ageandcity.You can't place this data into a
Map.Entrybecause it can only hold two references. A quick and dirty option would be to pack these properties into aListusingList.of()(Arrays.asList()for Java 8), or nest a map entry into another map entry (which would look very ugly). Although it's doable, I would not recommend doing so if you care about maintainability of code. Therefore, I'll not use this approach (but if you wish - just change one expression in the Collector).A cleaner way would be to introduce a
class, or a Java 16recordto represent these properties.Option with a record would be very concise because all the boilerplate code would be auto-generated by the compiler:
For JDK versions earlier than 16 you can use the following class:
To make the solution compliant with Java 8, I would use this class.
In order to group the data from the stream into a
Map, where instances of the class above would be used as a Key, we can use a flavor of CollectorgroupingBy(), which expects a keyMapper function and a downstream Collector responsible for generating the Values of the Map.Since we need to perform plain arithmetic using primitive values, it would be performancewise to make use of the Collector which performs a mutable reduction, i.e. mutates the properties of it's underlying mutable container while consuming elements from the stream. And to create such Collector a type that would serve as a mutable container.
I'll introduce a class
AggregatedValueswhich would serve as accumulation type. It would make sense to do so if objects in the source list represent different people (in such case the result would not represent a particular person, and you would useStudent, or whatever it's named in the real code, for that purpose it would be confusing), or ifStudentis immutable, and you want to keep it like that.And that's how we can make use of it:
If in your real code, it does make sense in your real code to have the same type of the resulting list we can do one small change by introducing method
toStudent()in theAggregatedValues.And this method can be used as a finisher Function of the Collector:
Output: