Java8 Streams - Compare Two List's object values and add value to sub object of first list?

Let listOne.size() is N and listTwo.size() is M. Then 2-for-loops solution has complexity of O(M*N).

We can reduce it to O(M+N) by indexing listTwo by ids.

Case 1 - assuming listTwo has no objects with the same id

// pair each id with its marks
Map<String, String> marksIndex = listTwo.stream().collect(Collectors.toMap(ObjectTwo::getId, ObjectTwo::getMarks));
// go through list of `ObjectOne`s and lookup marks in the index
listOne.forEach(o1 -> o1.setScore(marksIndex.get(o1.getId())));

Case 2 - assuming listTwo has objects with the same id

    final Map<String, List<ObjectTwo>> marksIndex = listTwo.stream()
            .collect(Collectors.groupingBy(ObjectTwo::getId, Collectors.toList()));

    final List<ObjectOne> result = listOne.stream()
            .flatMap(o1 -> marksIndex.get(o1.getId()).stream().map(o2 -> {
                // make a copy of ObjectOne instance to avoid overwriting scores
                ObjectOne copy = copy(o1);
                copy.setScore(o2.getMarks());
                return copy;
            }))
            .collect(Collectors.toList());

To implement copy method you either need to create a new object and copy fields one by one, but in such cases I prefer to follow the Builder pattern. It also results in more "functional" code.


Following code copies marks from ObjectTwo to score in ObjectOne, if both ids are equal, it doesn't have intermediate object List<ObjectOne> result

listOne.stream()
    .forEach(one -> {listTwo.stream()
        .filter(two -> {return two.getId().equals(one.getId());})
        .limit(1)
        .forEach(two -> {one.setScore(two.getMarks());});
    });

This should work.

Map<String, String> collect = listTwo.stream().collect(Collectors.toMap(ObjectTwo::getId, ObjectTwo::getMarks));
listOne
   .stream()
   .filter(item -> collect.containsKey(item.getId()))
   .forEach(item -> item.setScore(collect.get(item.getId())));