How to sum up the individual fields of the object list and return the results as a single object
You can use Stream.reduce
method for that, just need to create a NutrientsAggregator
that will know to add the values from FoodNutritional
to itself and sum everything
public class NutrientsAggregator {
private double calories;
private double totalFat;
private double saturatedFat;
private double cholesterol;
public NutrientsAggregator addFoodNutritionalValues(FoodNutritional foodNutrional) {
this.calories += foodNutrional.getCalories();
this.totalFat+= foodNutrional.getTotalFat();
this.saturatedFat+= foodNutrional.getSaturatedFat();
this.cholesterol+= foodNutrional.getCholesterol();
return this;
}
}
and than:
NutrientsAggregator result = response.getBody()
.stream()
.reduce(new NutrientsAggregator(),
(aggregator, food) -> aggregator.addFoodNutritionalValues(food);
Let introduce class NutritionAccumulator
:
class NutritionAccumulator{
private double fat = 0.;
private double carbs = 0.;
private double fiber = 0.;
private double protein = 0.;
public NutritionAccumulator() {
}
public NutritionAccumulator(double fat, double carbs, double fiber, double protein) {
this.fat = fat;
this.carbs = carbs;
this.fiber = fiber;
this.protein = protein;
}
public NutritionAccumulator add(NutritionAccumulator that){
return new NutritionAccumulator(this.fat + that.fat,
this.carbs + that.carbs,
this.fiber + that.fiber,
this.protein + that.protein
);
}
}
And now we can write simple stream reduce:
Optional.ofNullable(response.body())
.stream()
.reduce(
new NutritionAccumulator(),
(acc, fudNut) -> new NutritionAccumulator(
fudNut.getTotalFat(),
fudNut.getTotalCarbohydrate(),
fudNut.getDietaryFiber(),
fudNut.getProtein()
).add(acc),
NutritionAccumulator::add
);
And finally you can pass result from above to builder.
How about using a Supplier<Stream<T>>>
to create a reusable Stream
something like :
Supplier<Stream<FoodNutritional>> foodNutritionalSupplier = () -> Optional.ofNullable(responseBody)
.stream()
.flatMap(List::stream);
return Nutrients.builder()
.carbohydrates(foodNutritionalSupplier.get().mapToDouble(FoodNutritional::getTotalCarbohydrate).sum())
.protein(foodNutritionalSupplier.get().mapToDouble(FoodNutritional::getProtein).sum())
.fat(foodNutritionalSupplier.get().mapToDouble(FoodNutritional::getTotalFat).sum())
.dietaryFiber(foodNutritionalSupplier.get().mapToDouble(FoodNutritional::getDietaryFiber).sum())
.build();
where responseBody
corresponds to the response.getBody()
in the question.
Or further to using a utility to abstract out the core logic, such as :
private Nutrients nutrientsCalculator(List<FoodNutritional> responseBody) {
Supplier<Stream<FoodNutritional>> foodNutritionalSupplier =
() -> Optional.ofNullable(responseBody).stream().flatMap(List::stream);
// should ideally be as simple as 'responseBody::stream'
return Nutrients.builder()
.carbohydrates(sumNutrition(foodNutritionalSupplier, FoodNutritional::getTotalCarbohydrate))
.protein(sumNutrition(foodNutritionalSupplier, FoodNutritional::getProtein))
.fat(sumNutrition(foodNutritionalSupplier, FoodNutritional::getTotalFat))
.dietaryFiber(sumNutrition(foodNutritionalSupplier, FoodNutritional::getDietaryFiber))
.build();
}
private Double sumNutrition(Supplier<Stream<FoodNutritional>> foodNutritionalSupplier,
ToDoubleFunction<FoodNutritional> nutritionTypeFunction) {
return foodNutritionalSupplier.get().mapToDouble(nutritionTypeFunction).sum();
}