Issue
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>
Solution
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 inb
. Instead, we can index all theid
that are present in the listb
by placing them into a hash-based collection (that would allow to find out whether a particularid
is present in theb
in constant time) and associate eachid
with the correspondingquantity
. I.e. we can generateHashMap
, that maps eachid
in theb
to it'squantity
.Lists
a
andc
would be identical because they would contain the same references. That means there's no point in generating the listc
unless you want it to contain only elements havingid
that are present in the listb
.
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
.
Answered By - Alexander Ivanchenko
Answer Checked By - Marilyn (JavaFixing Volunteer)