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
ifbranch of the accumulator function:should be
Otherwise you would be adding an
Arrays.ArrayListto the list, but you expect the list to only containInventoryDetails . That's why theClassCastExceptionis thrown.If you had added the correct thing, you don't need to cast at all. You can delete
objectToInventorySkuDetailsFunctionentirely.Your
combinerfunction is also incorrect. You are supposed to merge the entirety ofl2intol1, but you are ignoring all the items inl2that does not also exist inl1.In any case, for learning purposes, I would collect to a
Mapinstead of aList. It is easier to write the accumulator and the combiner (you can just usemerge).Notice that both of your approaches changes the existing
InventoryDetailobjects. I changed that in the implementation above to create newInventoryDetailobjects instead.Also note that your code using
toMapdoesn't need the second stream. You can get the values of the map usingvalues():If you need to convert it to a
List, simply donew ArrayList<>(listFromToMap).