Search code examples
javalistjava-stream

Perform operation on first element and then all items of a list using the Java stream API


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"
        }
    ]
}

Solution

  • 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

    • allocate a map
    • Iterate over the testPojoList
    • Map.compute will take a key, and then if the value is null, create one. Otherwise, it uses the existing value.
    • that value is returned and can be used in the same construct to further modify the value. In this case it does the following:
      • create a new TransformedTestPojo instance with the key (company) and new ArrayList<>() for the personal details.
      • then return that list and get the personal details list and add a new Personal details instance.
    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.

    • stream the testPojoList
    • Use Collectors.groupingBy with company as the key
    • The associated list will be of PersonalDetails instances.
    • then stream the 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())))