Reputation: 389
I have a Java class:
class Person {
String firstName;
String lastName;
int income;
public Person(String firstName, String lastName, int income)
{
this.firstName = firstName;
this.lastName = lastName;
this.income = income;
}
I have a Collection<Person>
, with 4 x Person objects:
Collection<Person> persons = new ArrayList<>();
persons.add(new Person("John", "Smith", 5));
persons.add(new Person("Mary", "Miller", 2));
persons.add(new Person("John", "Smith", 4));
persons.add(new Person("John", "Wilson", 4));
I want to make a new Collection instance, but from the elements with the same "firstName" and "lastName", make 1 element, and the result "income" will be the sum of the "incomes" of each element. So, for this particular case, the resulting collection will have 3 elements, and "John Smith" will have the "income" = 9.
In SQL, the equivalent query is:
SELECT FIRSTNAME, LASTNAME, SUM(INCOME) FROM PERSON GROUP BY FIRSTNAME, LASTNAME
I found only answers which contain Map as result, and "key" contains the column(s) used for grouping by. I want to obtain directly a similar type of collection from the initial (ArrayList<Person>
), and not a Map, because if in my collection I have millions of elements, it will decrease the performance of the code. I know it was easier if I worked on SQL side, but in this case I must work on Java side.
Upvotes: 3
Views: 3139
Reputation: 1528
I found the answer below:
List<Person> collect = persons.stream()
.collect(Collectors.groupingBy(person -> person.getFirstName() + "." + person.getLastName(),
Collectors.summingInt(Person::getIncome)))
.entrySet().stream().map(entry -> new Person(entry.getKey().split(".")[0],
entry.getKey().split(".")[1],
entry.getValue()))
.collect(Collectors.toList());
Do not do that way! It uses memory a lot. Use Wrapper (PersonComparator) over the fields you need to group by.
public class Main {
public static void main(String[] args) {
Collection<Person> persons = new ArrayList<>();
persons.add(new Person("John", "Smith", 5));
persons.add(new Person("Mary", "Miller", 2));
persons.add(new Person("John", "Smith", 4));
persons.add(new Person("John", "Wilson", 4));
Map<Person, Integer> groupedByIncomes = persons.stream()
.collect(
Collectors.groupingBy(
Person::getPersonComparator,
Collectors.summingInt(Person::getIncome)
)
)
.entrySet()
.stream()
.collect(Collectors.toMap(
e -> e.getKey().person,
Map.Entry::getValue
));
System.out.println(groupedByIncomes);
}
static class Person {
String firstName;
String lastName;
int income;
public Person(String firstName, String lastName, int income) {
this.firstName = firstName;
this.lastName = lastName;
this.income = income;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getIncome() {
return income;
}
PersonComparator getPersonComparator() {
return new PersonComparator(this);
}
static class PersonComparator {
Person person;
PersonComparator(Person person) {
this.person = person;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersonComparator that = (PersonComparator) o;
if (!person.getFirstName().equals(that.person.getFirstName())) return false;
return person.getLastName().equals(that.person.getLastName());
}
@Override
public int hashCode() {
int result = person.getFirstName().hashCode();
result = 31 * result + person.getLastName().hashCode();
return result;
}
}
}
}
If you need framework solution f.e. when you need some abstraction over the data types you have (SQL, Mongo or Collections) I suggest you to use QueryDSL: http://www.querydsl.com/
Upvotes: 1
Reputation: 32145
I think JoSQL is your way to go here, it allow you to run SQL queries over java objects:
JoSQL (SQL for Java Objects) provides the ability for a developer to apply a SQL statement to a collection of Java Objects. JoSQL provides the ability to search, order and group ANY Java objects and should be applied when you want to perform SQL-like queries on a collection of Java Objects.
And this is how to use it in your case:
Query q=new Query();
q.parse("SELECT firstname, lastname, SUM(income) FROM package.Person GROUP BY firstname, lastname");
List<?> results=q.execute(names).getResults();
You can also follow this JoSQL tutorial for further reading.
Upvotes: 1
Reputation: 3947
I don't know if that is the most beautiful solution, but you can try to groupBy
firstName
and lastName
with a delimiter between them, let's say .
. After you collect your data into Map<String, Integer>
that contains your firstName.lastName
, you create new list of Person
from it.
List<Person> collect = persons.stream()
.collect(Collectors.groupingBy(person -> person.getFirstName() + "." + person.getLastName(),
Collectors.summingInt(Person::getIncome)))
.entrySet().stream().map(entry -> new Person(entry.getKey().split(".")[0],
entry.getKey().split(".")[1],
entry.getValue()))
.collect(Collectors.toList());
Upvotes: 2
Reputation: 14999
You can use Java 8 streams' Collector's groupingBy:
Map<String, Integer> sum = items.stream().collect(
Collectors.groupingBy(p -> p.getFirstName()+p.getSecondName(), Collectors.summingInt(Person::getIncome)));
Upvotes: 0