Filter Objects having the same property from two Lists and Update them and Store it in a 3rd List in Java 8

Viewed 47

I have the following class:

@AllArgsConstructor
@Getter
@Setter
public static class Manipulate {
    private int id;
    private int quantity;
}

And I have two lists a and b.

List<Manipulate> a = new ArrayList<>();
a.add(new Manipulate(1,100));
a.add(new Manipulate(2,200));

List<Manipulate> b = new ArrayList<>();
b.add(new Manipulate(1,10));
b.add(new Manipulate(2,20));

I need to filter these two lists based on the id property.

And I want to subtract quantities of objects contained in b from quantities of objects contained in a and store the result into a List.

My attempt:

List<Manipulate> c = a.stream().map(k -> {
    b.stream().filter(j -> j.getId() == k.getId())
        .forEach(i -> {
            int i1 = k.getQuantity() - i.getQuantity();
            k.setQuantity(i1);
        });
    return k;
});

I'm getting the following compilation error:

Required type: List <Manipulate> Provided: Stream<Object>
no instance(s) of type variable(s) R exist so that Stream<R> conforms to List<Manipulate>
1 Answers

There are several issues with your code:

  • map() is an intermediate operation, it means that it doesn't produce the resulting value but returns another stream. In order to produce a result from the stream you need to apply a terminal operation (e.g. collect(), reduce(), findFirst()). For more information, refer to the API documentation.

  • In Functional programming, it's not a good practice to mutate arguments passed to a function (and that's what you're doing inside the map()).

  • Your code is based on a brute-force logic (which always imply the worst possible performance): for every element in a iterate over the all elements in b. Instead, we can index all the id that are present in the list b by placing them into a hash-based collection (that would allow to find out whether a particular id is present in the b in constant time) and associate each id with the corresponding quantity. I.e. we can generate HashMap, that maps each id in the b to it's quantity.

  • Lists a and c would be identical because they would contain the same references. That means there's no point in generating the list c unless you want it to contain only elements having id that are present in the list b.

That's how your code might be reimplemented:

List<Manipulate> a = // initializing list a
List<Manipulate> b = // initializing list b

// mapping each `id` in the `b` to it's `quantity`

Map<Integer, Integer> quantityById = b.stream()
    .collect(Collectors.toMap(
        Manipulate::getId,      // keyMapper
        Manipulate::getQuantity // valueMapper
    ));

// generating the list `c`, containing only elements
// with `id` that are present in the `b`
        
List<Manipulate> c = a.stream()
    .filter(m -> quantityById.containsKey(m.getId()))
    .collect(Collectors.toList()); // or .toList() for Java 16+

// updating `quantity` property of each element in `c`
    
for (Manipulate m : c)
    m.setQuantity(
        m.getQuantity() - quantityById.get(m.getId())
    );

In case if you had no intention to change the data in a, then you need to create new instances of Manipulate for every matching id. And it's perfectly fine to do in the stream:

List<Manipulate> a = // initializing list a
List<Manipulate> b = // initializing list b
Map<Integer, Integer> quantityById = // generate a map like shown above

List<Manipulate> c = a.stream()
    .filter(m -> quantityById.containsKey(m.getId()))
    .map(m -> new Manipulate(m.getId(), m.getQuantity() - quantityById.get(m.getId())))
    .collect(Collectors.toList()); // or .toList() for Java 16+

Note: you need to add the third argument to Collectors.toMap() in case if there could be duplicated id in the list b.

Related