9

I have two different lists of same objects but different properties and with a common identifier in those objects. I would like to iterate over the first list and get the corresponding object from the second (which has common properties) and then wrap those objects around and finally add that object to a list using Java Streams.

This is the example I have taken.

private class Person {
        private String name;
        private boolean isSenior;

        private Person(String name, boolean isSenior) {
            this.name = name;
            this.isSenior = isSenior;
        }

        public String getName() {
            return name;
        }

        public boolean isSenior() {
            return isSenior;
        }

        @Override
        public String toString() {
            return name + ": " + isSenior;
        }
    }

    private class PersonWrapper {
        private Person jrPerson;
        private Person srPerson;

        private PersonWrapper(Person jrPerson, Person srPerson) {
            this.jrPerson = jrPerson;
            this.srPerson = srPerson;
        }

        public Person getJrPerson() {
            return jrPerson;
        }

        public Person getSrPerson() {
            return srPerson;
        }

        @Override
        public String toString() {
            return jrPerson.toString() + "-" + srPerson.toString();
        }
    }

Now, in the main class, I will create two list instances like this

List<Person> jrPersons = new ArrayList<>();
List<Person> srPersons = new ArrayList<>();

and add the objects in the following manner

jrList.add(new Person("John", false));
jrList.add(new Person("Paul", false));
jrList.add(new Person("Mike", false));

seniorList.add(new Person("John", true));
seniorList.add(new Person("Paul", true));
seniorList.add(new Person("Mike", true));

Now, I want to iterate over the jrList and find the corresponding Person object in the srList (same name). Then I would wrap these objects as PersonWrapper and that object to a list.

So far, this is what I have been doing

List<PersonWrapper> wrapperList = new ArrayList<>();

jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName())).map(sr -> new PersonWrapper(jr, sr)).collect(Collectors.toList()));

Now, I would like to know how the Collectors.toList() can be substituted by wrapperList or how the output from Collectors.toList() be added to wrapperList.

Please help me in achieving this.

3 Answers 3

12

Instead of using a forEach just use streams from the beginning:

List<PersonWrapper> wrapperList = jrList.stream()
    .flatMap(jr -> seniorList.stream()
         .filter(sr -> jr.getName().equals(sr.getName()))
         .map(sr -> new PersonWrapper(jr, sr))
    )
    .collect(Collectors.toList());

By using flatMap you can flatten a stream of streams (Stream<Stream<PersonWrapper>>) into a single stream (Stream<PersonWrapper>)

If you can't instantiate wrapperList by yourself or really need to append to it. You can alter above snippet to following:

List<PersonWrapper> wrapperList = new ArrayList<>();

jrList.stream()
    .flatMap(jr -> seniorList.stream()
         .filter(sr -> jr.getName().equals(sr.getName()))
         .map(sr -> new PersonWrapper(jr, sr))
    )
    .forEach(wrapperList::add);
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks @Lino. That served my purpose
can you also please explain how Stream<Stream<PersonWrapper>> is being created? By which operation in the above expression exactly? @Lino
@Ravi In the first snippet, you create a stream in the first line. Now imagine in the second line, instead of using flatMap use map. In the map operation we create another stream, filter that, and then map the results to a new PersonWrapper instance. You never terminate the stream, you're just adding opperations. So you're in fact mapping a Person to a Stream<PersonWrapper> which finally results in a Stream<Stream<PersonWrapper>>.
Thanks @Lino. Understood now. Also, can I know why adding findFirst().get() before map(sr -> new PersonWrapper(jr, sr)) is giving me compile time error saying "Stream<Object>" cannot be converted to Unknown"?
Well I guess that is because flatMap expects a Stream to be returned. But by using findFirst().get() you're trying to return a PersonalWrapper
|
4

While Lino's answer is certainly correct. I would argue that if a given person object in jrList can only ever have one corresponding match in seniorList maximum, in other words, if it's a 1-1 relationship then you can improve upon the solution given by Lino by finding the first match as follows:

List<PersonWrapper> resultSet = jrList.stream()
                .map(p -> seniorList.stream()
                        .filter(sr -> p.getName().equals(sr.getName()))
                        .findFirst()
                        .map(q -> new PersonWrapper(p, q))
                        .get())
                .collect(Collectors.toList());

or if there is no guarantee that each person in jrList will have a corresponding match in seniorList then change the above query to:

List<PersonWrapper> resultSet = jrList.stream()
                .map(p -> seniorList.stream()
                        .filter(sr -> p.getName().equals(sr.getName()))
                        .findFirst()
                        .map(q -> new PersonWrapper(p, q))
                        .orElse(null))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

The difference is that now instead of calling get() on the result of findFirst() we provide a default with orElse in case findFirst cannot find the corresponding value and then we filter the null values out in the subsequent intermediate operation as they are not needed.

3 Comments

thanks for the new answer. I was trying to accomplish this requirement, but I couldn't do it so. Now, it is clear.
As of Java9 you might want to omit the orElse() and just use .flatMap(p -> seniorList.stream() .filter(sr -> p.getName().equals(sr.getName())) .findFirst() .map(q -> new PersonWrapper(p, q)).stream() which flattens the optional to a stream
@Lino good shout, in which case we no longer need the subsequent filter for null elements. I had to restrict myself here as the question is tagged with java-8 hence didn’t mention anything about java-9. But yeah that’s a good shout. Thanks!
-1

Replace your looping logic with below code.

 jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName()))
                    .map(sr -> wrapperList.add(new PersonWrapper(jr, sr))).collect(Collectors.toList()));

2 Comments

using a side-effect in a map is not recommended. Also collecting to a list doesn't make sense as a terminal operation
This sounds like it works. Thanks @Dharita. Considering Lino's comment, I think his answer makes more sense

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.