I want to iterate a list of objects 2 times. The first time I need to use only the first object and perform some operation. The second time I want to perform operation on all the items of list.
In the below example, I have a list of TestPojo
named testPojoList
.
Using the Java stream API I have tried to achieve the same in 2 steps (operation #1 and operation #2) Is there a better way or can I merge both of the operations in single operation? The code is below:
public void Mapping() {
TestPojo testPojo1 = TestPojo.builder().name("Mike").city("Denver").company("Tesla").build();
TestPojo testPojo2 = TestPojo.builder().name("Bob").city("Atlanta").company("Tesla").build();
TestPojo testPojo3 = TestPojo.builder().name("Steve").city("Chicago").company("Tesla").build();
TestPojo testPojo4 = TestPojo.builder().name("John").city("Boston").company("Tesla").build();
List<TestPojo> testPojoList = Arrays.asList(testPojo1, testPojo2, testPojo3, testPojo4);
//Operation1
TransformedTestPojo transformedTestPojo = testPojoList.stream().findFirst().map(testPojo -> mapCompanyName(testPojo)).orElse(null);
//Operation2
List<PersonalDetails> personalDetailsList = testPojoList.stream().map(testPojo -> mapOtherDetails(testPojo)).collect(Collectors.toList());,
transformedTestPojo.setPersonalDetailsList(personalDetailsList);
System.out.println(transformedTestPojo);
}
private PersonalDetails mapOtherDetails(TestPojo testPojo) {
return PersonalDetails.builder().name(testPojo.getName()).City(testPojo.getCity()).build();
}
private TransformedTestPojo mapCompanyName(TestPojo testPojo) {
return TransformedTestPojo.builder().company(testPojo.getCompany()).build();
}
public class TestPojo {
String name;
String city;
String company;
}
public class TransformedTestPojo {
String company;
List<PersonalDetails> personalDetailsList;
}
public class PersonalDetails {
String name;
String City;
}
The following are the input and desired output:
Request list:
{
"testPojoList": [
{
"name": "Mike",
"city": "Denver",
"company": "Tesla"
},
{
"name": "Bob",
"city": "Atlanta",
"company": "Tesla"
},
{
"name": "Steve",
"city": "Chicago",
"company": "Tesla"
},
{
"name": "John",
"city": "Boston",
"company": "Tesla"
}
]
}
Response object:
"TransformedTestPojo":
{
"company": "Tesla",
"personalDetailsList": [
{
"name": "Mike",
"City": "Denver"
},
{
"name": "Bob",
"City": "Atlanta"
},
{
"name": "Steve",
"City": "Chicago"
},
{
"name": "John",
"City": "Boston"
}
]
}
Here are two ways to do it. But first, I created the classes. Instead of a builder I used a constructor for TestPojo
. But it will still work.
class TestPojo {
String name;
String city;
String company;
public TestPojo(String name, String city, String company) {
this.name = name;
this.city = city;
this.company = company;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
public String getCompany() {
return company;
}
@Override
public String toString() {
return String.format("[%s, %s, %s]", name, city, company);
}
}
class TransformedTestPojo {
String company;
List<PersonalDetails> personalDetailsList;
public TransformedTestPojo(String company,
List<PersonalDetails> personalDetailsList) {
this.company = company;
this.personalDetailsList = personalDetailsList;
}
public String getCompany() {
return company;
}
public List<PersonalDetails> getPersonalDetailsList() {
return personalDetailsList;
}
}
class PersonalDetails {
String name;
String City;
public PersonalDetails(String name, String city) {
this.name = name;
City = city;
}
@Override
public String toString() {
return String.format("[%s, %s]", name, City);
}
}
The Data
List<TestPojo> testPojoList =
List.of(new TestPojo("Mike", "Denver", "Tesla"),
new TestPojo("Bob", "Atlanta", "Tesla"),
new TestPojo("Steve", "Chicago", "Tesla"),
new TestPojo("John", "Boston", "Tesla"));
The Map approach using a loop
The best approach (imo) is to use Java 8 features of the Map
interface
testPojoList
Map.compute
will take a key, and then if the value is null, create one. Otherwise, it uses the existing value.TransformedTestPojo
instance with the key (company)
and new ArrayList<>()
for the personal details.Map<String, TransformedTestPojo> map = new HashMap<>();
for (TestPojo tp : testPojoList) {
map.compute(tp.getCompany(),
(k, v) -> v == null ? new TransformedTestPojo(k,
new ArrayList<>()) : v)
.getPersonalDetailsList().add(new PersonalDetails(
tp.getName(), tp.getCity()));
}
Once the map has been created, get the map values (which has the TransformedTestPojo
instances) and return as a collection.
Collection<TransformedTestPojo> collection = map.values();
Note that a Collection (super type of List) , not a List is created. If a list is required you can do the following.
List<TransformedTestPojo> list = new ArrayList<>(map.values());
Displaying the results
list.forEach(k -> {
System.out.println(k.getCompany());
k.getPersonalDetailsList()
.forEach(p -> System.out.println(" " + p));
});
prints
Tesla
[Mike, Denver]
[Bob, Atlanta]
[Steve, Chicago]
[John, Boston]
Here is a stream solution.
Collectors.groupingBy
with company as the keyPersonalDetails
instances.entrySet
of the map
and build a list of TransformedTestPojo
List<TransformedTestPojo> list1 = testPojoList.stream()
.collect(Collectors.groupingBy(TestPojo::getCompany,
Collectors.mapping(
tp -> new PersonalDetails(
tp.getName(), tp.getCity()),
Collectors.toList())))
.entrySet().stream()
.map(e -> new TransformedTestPojo(e.getKey(),
e.getValue()))
.toList();
}
Note that the map itself could be used instead of a returned List<TransformedTestPojo>
. The key
is the company name
and the value contains the list of PersonalDetails
. If that is useful, then all you need is the following:
Map<String, List<PersonalDetails>> result = testPojoList.stream()
.collect(Collectors.groupingBy(TestPojo::getCompany,
Collectors.mapping(
tp -> new PersonalDetails(
tp.getName(), tp.getCity()),
Collectors.toList())))