I'm trying to get the quantity of the object summed up if the name of the object matches.
The POJO details are as below:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InventoryDetail {
private String name;
private int quantity;
}
The code is as follows:
public class InventoryDetailsCollationUpdateQuantityForSameName {
public static void main(String[] args) {
List<InventoryDetail> inventoryDetails = Arrays.asList(
new InventoryDetail("iPhone", 1),
new InventoryDetail("Samsung", 1),
new InventoryDetail("iPhone", 2),
new InventoryDetail("Motorolla", 1),
new InventoryDetail("Nokia", 1),
new InventoryDetail("iPhone", 1)
);
Function<Object, InventoryDetail> objectToInventorySkuDetailsFunction = o -> (InventoryDetail) o;
//This works but want to know the other way to do with the .collect
List<InventoryDetail> listFromToMap = inventoryDetails.stream()
.map(objectToInventorySkuDetailsFunction)
.collect(Collectors.toMap(InventoryDetail::getName,
Function.identity(),
(is1, is2) -> {
is1.setQuantity(is1.getQuantity() + is2.getQuantity());
return is1;
}))
.entrySet().stream()
.map(stringInventoryDetailEntry -> stringInventoryDetailEntry.getValue())
.collect(Collectors.toList());
System.out.println(listFromToMap);
ArrayList<Object> listFromCollect = inventoryDetails.stream().collect(ArrayList::new,
(list, newInventorySkuDetail) -> {
if (list.isEmpty() || list.stream().map(objectToInventorySkuDetailsFunction)
.noneMatch(existingInventorySkuDetail -> StringUtils.equalsIgnoreCase(existingInventorySkuDetail
.getName(), newInventorySkuDetail.getName()))) {
list.add(inventoryDetails);
} else {
list.stream()
.map(objectToInventorySkuDetailsFunction)
.filter(existingInventorySkuDetail -> StringUtils.equalsIgnoreCase(existingInventorySkuDetail
.getName(), newInventorySkuDetail.getName()))
.forEach(existingInventorySkuDetail -> existingInventorySkuDetail
.setQuantity(existingInventorySkuDetail.getQuantity() + newInventorySkuDetail.getQuantity()));
}
}
, (l1, l2) -> {
l2.stream()
.map(objectToInventorySkuDetailsFunction)
.forEach(inventorySkuDetailToBeChecked -> {
if (l1.stream().map(objectToInventorySkuDetailsFunction)
.anyMatch(areInventorySkuDetailSame(inventorySkuDetailToBeChecked))) {
l1.stream()
.map(objectToInventorySkuDetailsFunction)
.filter(areInventorySkuDetailSame(inventorySkuDetailToBeChecked))
.forEach(existingInventorySkuDetail -> existingInventorySkuDetail
.setQuantity(existingInventorySkuDetail.getQuantity() + inventorySkuDetailToBeChecked.getQuantity()));
}
});
}
);
System.out.println(listFromCollect);
}
private static Predicate<InventoryDetail> areInventorySkuDetailSame(InventoryDetail newInventoryDetail) {
return existingInventoryDetail -> StringUtils.equalsIgnoreCase(existingInventoryDetail.getName(), newInventoryDetail.getName());
}
private static Consumer<InventoryDetail> addToQuantityAsNeeded(InventoryDetail inventoryDetailToBeChecked) {
return existingInventoryDetail -> existingInventoryDetail.setQuantity(existingInventoryDetail.getQuantity() + inventoryDetailToBeChecked.getQuantity());
}
}
The expected output is as below from listFromToMap, but I need to get it in listFromCollect. Please help me understand what the issue is
[InventoryDetail(name=Apple, quantity=4), InventoryDetail(name=Samsung, quantity=1), InventoryDetail(name=Nokia, quantity=1), InventoryDetail(name=Motorola, quantity=1)]
When trying to run the above code I get the below error
Exception in thread "main" java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to com.learning.java.pojo.InventoryDetail
at com.learning.java.eight.stream.InventoryDetailsCollationUpdateQuantityForSameName.lambda$main$0(InventoryDetailsCollationUpdateQuantityForSameName.java:23)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:230)
at java.util.stream.MatchOps$MatchOp.evaluateSequential(MatchOps.java:196)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
at com.learning.java.eight.stream.InventoryDetailsCollationUpdateQuantityForSameName.lambda$main$6(InventoryDetailsCollationUpdateQuantityForSameName.java:42)
at java.util.stream.ReduceOps$4ReducingSink.accept(ReduceOps.java:220)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:510)
at com.learning.java.eight.stream.InventoryDetailsCollationUpdateQuantityForSameName.main(InventoryDetailsCollationUpdateQuantityForSameName.java:39)
Please help me with a clean way to get the list of InventtorySku
with summed up quantity if the name is the same.
You seem to have made a typo in the if
branch of the accumulator function:
list.add(inventoryDetails);
should be
list.add(newInventorySkuDetail);
Otherwise you would be adding an Arrays.ArrayList
to the list, but you expect the list to only contain InventoryDetail
s . That's why the ClassCastException
is thrown.
If you had added the correct thing, you don't need to cast at all. You can delete objectToInventorySkuDetailsFunction
entirely.
Your combiner
function is also incorrect. You are supposed to merge the entirety of l2
into l1
, but you are ignoring all the items in l2
that does not also exist in l1
.
In any case, for learning purposes, I would collect to a Map
instead of a List
. It is easier to write the accumulator and the combiner (you can just use merge
).
BiFunction<InventoryDetail, InventoryDetail, InventoryDetail> merge = (detail1, detail2) ->
new InventoryDetail(detail1.getName(), detail1.getQuantity() + detail2.getQuantity());
Collection<InventoryDetail> listFromCollect = inventoryDetails.stream().collect(
HashMap<String, InventoryDetail>::new,
(map, detail) -> map.merge(detail.getName(), detail, merge),
(m1, m2) -> m2.forEach((name, detail) -> m1.merge(name, detail, merge))
).values();
Notice that both of your approaches changes the existing InventoryDetail
objects. I changed that in the implementation above to create new InventoryDetail
objects instead.
Also note that your code using toMap
doesn't need the second stream. You can get the values of the map using values()
:
BinaryOperator<InventoryDetail> merge = (detail1, detail2) ->
new InventoryDetail(detail1.getName(), detail1.getQuantity() + detail2.getQuantity());
Collection<InventoryDetail> listFromToMap = inventoryDetails.stream()
.collect(Collectors.toMap(
InventoryDetail::getName,
Function.identity(),
merge))
.values();
If you need to convert it to a List
, simply do new ArrayList<>(listFromToMap)
.