My question is an extension of this question.
To get a list of attributes we can use this code:
List<String> namesList = personList.stream()
.map(Person::getName)
.collect(Collectors.toList());
However if I want to get 100 different attributes.
Option #1:
List<Integer> ageList = personList.stream()
.map(Person::getAge)
.collect(Collectors.toList());
List<String> surnamesList = personList.stream()
.map(Person::getSurname)
.collect(Collectors.toList());
List<String> fathersNamesList = personList.stream()
.map(Person::getFathersName)
.collect(Collectors.toList());
List<String> mothersNamesList = personList.stream()
.map(Person::getMothersName)
.collect(Collectors.toList());
then would it be better to rewrite this line 100 times or should I simply run 1 loop and collect all the attributes there.
Option #2:
for(Person person : persons) {
namesList.add(person.getName());
surnamesList.add(person.getSurname());
fathersNamesList.add(person.getFathersName());
...
}
CodePudding user response:
Just use a loop:
// Allocate correct size to avoid resizing
List<String> names = new ArrayList<>(persons.size());
List<String> surnames = new ArrayList<>(persons.size());
List<String> ages = new ArrayList<>(persons.size());
// etc
for (Person person : persons) {
names.add(person.getName);
surnames.add(person.getSurname);
ages.add(person.getAge);
// etc
}
This will out-perform using streams by a very large margin, and it's easy to read and understand.
Minor note: Avoid including the type of a variable in its name, ie prefer names
over namesList
. Hungarian notation in any form is an anti-pattern.
CodePudding user response:
Duplicating the same logic isn't an option.
For that purpose it's way better to create a generic method that takes two arguments a source list and a function:
public static void main(String[] args) {
List<Integer> ageList = getListOfAttributes(personList, Person::getAge);
List<String> surnamesList = getListOfAttributes(personList, Person::getSurName);
}
public static <T, R> List<R> getListOfAttributes(List<T> source,
Function<T, R> func) {
return source.stream()
.map(func)
.collect(Collectors.toList());
}
CodePudding user response:
With Streams, a solution that is closer to the loop approach is to create a custom aggregate type:
class AggregatedPersonDetails {
List<Integer> ageList = new ArrayList<>();
List<String> surnamesList = new ArrayList<>();
List<String> fathersnamesList = new ArrayList<>();
List<String> mothersnamesList = new ArrayList<>();
public void add(Person person) {
ageList.add(person.getAge());
surnamesList.add(person.getSurName());
fathersnamesList.add(person.getFathersName());
mothersnamesList.add(person.getMothersName());
}
public AggregatedPersonDetails merge(AggregatedPersonDetails other) {
ageList.addAll(other.ageList);
surnamesList.addAll(other.surnamesList);
fathersnamesList.addAll(other.fathersnamesList);
mothersnamesList.addAll(other.mothersnamesList);
return this;
}
}
which you would use as:
personList.stream().collect(
AggregatedPersonDetails::new,
AggregatedPersonDetails::add,
AggregatedPersonDetails::merge);
This allows to do just one iteration.
If you need to do this at multiple places, you can extract a reusable Collector
from it:
Collector<Person, AggregatedPersonDetails, AggregatedPersonDetails> collector =
Collector.of(AggregatedPersonDetails::new, AggregatedPersonDetails::add, AggregatedPersonDetails::merge);
(otherwise you can change the merge()
method to return void
)