This is a bit of a fundamental question with an example project, as I'm still learning the best practices of Java 8 features.
Say I have an Order
object, that cointains a List
of OrderDetail
. At the same time, OrderDetail
contains a source
and a destiny
, along with quantity
and product
. OrderDetail
also has an order
(fk).
For this example, I will be moving Product
s from source
to destiny
, which both are ProductWarehouse
objects with an availableStock
property, which will be affected by the result of the Order
.
Now, I need to update the availableStock
for all the source
s and destiny
-ies. The stock should increase for destiny
-ies and decrease for source
s by the quantity
of the OrderDetail
. Since destiny
and source
are of the same type I can update them at the same time. The problem is that, of course, they are different properties.
My first thought was as follows:
//Map keeps record of the sum of products moved from the source. Key is the id the Product
HashMap<Integer, Double> quantities;
ArrayList<OrderDetail> orderDetails;
/**
* This could've been a single stream, but I divided it in two for readability here
* If not divided, the forEach() method from ArrayList<> might been enough though
*/
public void updateStock() {
List<ProductWarehouse> updated = new ArrayList<>();
orderDetails.stream()
.map(OrderDetail::getSource)
.forEach(src -> {
src.setAvailableStock(src.getAvailableStock - quantities.get(src.getProductId()));
updated.add(src);
});
orderDetails.stream()
.map(OrderDetail::getDestiny)
.forEach(dst -> {
dst.setAvailableStock(dst.getAvailableStock + quantities.get(dst.getProductId()));
updated.add(dst);
});
productWarehouseRepository.save(updated);
}
While this works, there is a problem: This is like the example in the Javadoc about "unnecesary side efects". It is not strictly the same, but given how similar they are makes me think I'm taking avoidable risks.
Another option I thought of is using peek()
which has it's own implications since that method was thought mainly as a debugging tool, according to the javadoc.
HashMap<Integer, Double> quantities;
ArrayList<OrderDetail> orderDetails;
/**
* Either make it less readable and do one save or do one save for source and one for destiny
* I could also keep the updated list from before and addAll() the result of each .collect()
*/
public void updateStock() {
productWarehouseRepository.save(
orderDetails.stream()
.map(/*Same as above*/)
.peek(src -> {/*Same as above*/})
.collect(toList()) //static import
);
}
I've also read in a few blog post around that peek
should be avoided. This, again, because the doc says what it's usage should be (mostly).
Hence the question: If I'd want to modify the properties of a Collection
using the Stream
API, what would be the best way to do so, following best-practices? For an example like this, is it better to use Iterable.forEach()
or a simple enhanced for-loop? I honestly don't see where the side-effect might arise with operations like these using Stream
, but that's probably due lack of experience and understanding of the API.
The code in this quesiton is just for example purposes, but if it's easier to understand with models or more info, I can add it.