Let's say I have a class Item like this:
class Item {
private long id;
private BigDecimal value1;
private BigDecimal value2;
private BigDecimal value3;
}
Then I have a list with many itens, I want to know the sum of each of the values:
So, I know I could do something like
BigDecimal v1 = list.stream().map(Item::value1).reduce(BigDecimal.ZERO, BigDecimal::add);
However, this way I would need to do the same for each value, I'd like to know if there's some way of summing each attribute into only one Dto like:
class TotalItem {
private BigDecimal value1;
private BigDecimal value2;
private BigDecimal value3;
}
TotalItem t = list.stream().map(???).reduce(BigDecimal.ZERO, BigDecimal::add);
Is this possible?
thanks in advance.
I didn't test it but I think that you can implement add function on Item like:
public Item add(Item other) {
Item newItem = new Item(this.value1 + other.value1,
this.value2 + other.value2,
this.value3 + other.value3);
return newItem;
}
and then do:
Item t = list.stream().reduce(BigDecimal.ZERO, Item::add);
How about the following way?
TotalItem t = new TotalItem();
list.stream().forEach(item -> {
t.value1+ = item.value1;
t.value2+ = item.value2;
t.value3+ = item.value3;
});
I'm making the assumption that Item/TotalItem are very large objects, which would make writing a toTotalItem and a summarise(TotalItem,TotalItem) by hand a large and laborious job. One that is completely boilerplate and easy to get wrong.
Change the data structure to be a list or map - This makes summarisation simpler, at the cost of readability of the code and type safety.
Use reflection to iterate over the fields.
TotalItem toTotalItem(Item item) {
Field[] targetFields = TotalItem.class.getFields();
Collection<Field> sourceFields = Item.class.getFields().stream()
.filter(x=>fieldNameIsIn(x, targetFields)
.collect(Collectors.toList());
TotalItem returnItem = new TotalItem();
for(Field field : sourceFields) {
toTargetField(field, targetFields).set(returnItem, (BigDecimal) field.get(item));
}
return returnItem;
}
boolean fieldNameIsIn(Field sourceField, Field[] targetFields) // exercise for the reader
Field toTargetField(Field sourceField, Field[] targetFields) // exercise for the reader
This code above is not neat, but should show the concept. The same concept could be used to summarise.
This reduces the amount of code you need to write, but at the cost of runtime speed. Reflection is also hard to get right and magic (which some developers do not like).
The faster option would be a custom annotation which adds in summarisation. However this would be large chunk of work. If you have a large number of objects that need this, then it may make sense. Like reflection it is hard to get right and magic (which some developers do not like). Luckily you do not need a build step as Javac supports annotation processing natively.
This answer is inspired by JDK way of doing similar operations. Namely, I'll be referencing DoubleSummaryStatistics class.
First, we define a holder for information about BigDecimals we'll be collecting:
public class BigDecimalSummaryStats {
private long count;
private MathContext mc;
private BigDecimal sum = BigDecimal.ZERO;
private BigDecimal max;
private BigDecimal min;
public BigDecimalSummaryStats(MathContext mathCtx) {
mc = requireNonNull(mathCtx);
}
public Supplier<BigDecimalSummaryStats> supplier(MathContext ctx) {
return () -> new BigDecimalSummaryStats(ctx);
}
public void accept(BigDecimal value) {
requireNonNull(value);
count++;
sum = sum.add(value, mc);
min = min.min(value);
max = max.max(value);
}
public void combine(BigDecimalSummaryStats other) {
requireNonNull(other);
count += other.count;
sum = sum.add(other.sum, mc);
min = min.min(other.min);
max = max.max(other.max);
}
public long getCount() {
return count;
}
public BigDecimal getSum() {
return sum;
}
public BigDecimal getMax() {
return max;
}
public BigDecimal getMin() {
return min;
}
public BigDecimal getAverage() {
long c = getCount();
return c == 0 ? BigDecimal.ZERO : getSum().divide(BigDecimal.valueOf(c), mc);
}
}
This will provide with nice general utility suitable for collecting summary from arbitrary sequences of BigDecimal values.
Then, we can define a Summary class for our Item:
public class ItemSummaryStats {
private BigDecimalSummaryStats value1;
private BigDecimalSummaryStats value2;
private BigDecimalSummaryStats value3;
// ... other fields as needed
public ItemSummaryStats(MathContext math) {
value1 = new BigDecimalSummaryStats(math);
value2 = new BigDecimalSummaryStats(math);
value3 = new BigDecimalSummaryStats(math);
}
public void accept(Item item) {
value1.accept(item.value1);
value2.accept(item.value2);
value3.accept(item.value3);
// ... other fields as needed
}
public void combine(ItemSummaryStats other) {
value1.combine(other.value1);
value2.combine(other.value2);
value3.combine(other.value3);
}
public TotalItem get(
Function<BigDecimalSummaryStats, BigDecimal> v1Mapper,
Function<BigDecimalSummaryStats, BigDecimal> v2Mapper,
Function<BigDecimalSummaryStats, BigDecimal> v3Mapper) {
TotalItem t = new TotalItem();
t.value1 = v1Mapper.get(value1);
t.value2 = v2Mapper.get(value2);
t.value3 = v3Mapper.get(value3);
return t;
}
public TotalItem getSum() {
return get(BigDecimalSummaryStats::getSum,
BigDecimalSummaryStats::getSum,
BigDecimalSummaryStats::getSum);
}
public TotalItem getAverage() {
return get(BigDecimalSummaryStats::getAverage,
BigDecimalSummaryStats::getAverage,
BigDecimalSummaryStats::getAverage);
}
public TotalItem getMin() {
return get(BigDecimalSummaryStats::getMin,
BigDecimalSummaryStats::getMin,
BigDecimalSummaryStats::getMin);
}
//.... other methods basically all the same. You get the idea.
}
And finally we use this goodness like this:
TotalItem totals = list.stream().collect(
Collector.of(() -> new ItemStatsSummary(MathContext.DECIMAL64),
ItemStatsSummary::accept,
ItemStatsSummary::combine,
ItemStatsSummary::getSum)
)
Cons of this approach:
Slightly longer development time than adhoc solutions.
Are far outweighed by the pros, or at least I'm convinced of it:
Follows separation of concerns principle: Item Stats are not actually concerned how to collect summary of specific field: they can trust that BigDecimalSummary works
Testable: Each part can be tested in its own suite. You can trust that every field will work the same since they use the same API.
Flexible: get(Function...) method exposes a big list of possibilities: you can collect sum of first field, an averabe of second and min of third if needed.
Related
I'm trying to use Java 8 streams to create a single CarData object, which consists of an average of all the CarData fields in the list coming from getCars;
CarData = new CarData();
CarData.getBodyWeight returns Integer
CarData.getShellWeight returns Integer
List<CarData> carData = carResults.getCars();
IntSummaryStatistics averageBodyWeight = carData.stream()
.mapToInt((x) -> x.getBodyWeight())
.summaryStatistics();
averageBodyWeight.getAverage();
IntSummaryStatistics averageShellWeight = carData.stream()
.mapToInt((x) -> x.getShellWeight())
.summaryStatistics();
getShellWeight.getAverage();
I don't want to have to put each of these back together in my final returned result.
Visually, this is my list
getCars() : [
{CarData: { getBodyWeight=10, getShellWeight=3 } }
{CarData: { getBodyWeight=6, getShellWeight=5 } }
{CarData: { getBodyWeight=8, getShellWeight=19 } }
]
and the output I'm trying to achieve is a single object that has the average of each of the fields I specify. not sure If I need to use Collectors.averagingInt or some combo of IntSummaryStatistics to achieve this. Easy to do across one field for either of these techniques, just not sure what I'm missing when using multiple integer fields.
{CarData: { getBodyWeight=8, getShellWeight=9 } }
Starting with JDK 12, you can use the following solution:
CarData average = carData.stream().collect(Collectors.teeing(
Collectors.averagingInt(CarData::getBodyWeight),
Collectors.averagingInt(CarData::getShellWeight),
(avgBody, avgShell) -> new CarData(avgBody.intValue(), avgShell.intValue())));
For older Java versions, you can do either, add the teeing implementation of this answer to your code base and use it exactly as above or create a custom collector tailored to your task, as shown in Andreas’ answer.
Or consider that streaming twice over a List in memory is not necessarily worse than doing two operations in one stream, both, readability- and performance-wise.
Note that calling intValue() on Double objects has the same behavior as the (int) casts in Andreas’ answer. So in either case, you have to adjust the code if other rounding behavior is intended.
Or you consider using a different result object, capable of holding two floating point values for the averages.
You need to write your own Collector, something like this:
class CarDataAverage {
public static Collector<CarData, CarDataAverage, Optional<CarData>> get() {
return Collector.of(CarDataAverage::new, CarDataAverage::add,
CarDataAverage::combine,CarDataAverage::finish);
}
private long sumBodyWeight;
private long sumShellWeight;
private int count;
private void add(CarData carData) {
this.sumBodyWeight += carData.getBodyWeight();
this.sumShellWeight += carData.getShellWeight();
this.count++;
}
private CarDataAverage combine(CarDataAverage that) {
this.sumBodyWeight += that.sumBodyWeight;
this.sumShellWeight += that.sumShellWeight;
this.count += that.count;
return this;
}
private Optional<CarData> finish() {
if (this.count == 0)
return Optional.empty();
// adjust as needed if averages should be rounded
return Optional.of(new CarData((int) (this.sumBodyWeight / this.count),
(int) (this.sumShellWeight / this.count)));
}
}
You then use it like this:
List<CarData> list = ...
Optional<CarData> averageCarData = list.stream().collect(CarDataAverage.get());
Java beginner here. I'm supposed to find the best result a participant has in an event (and later compare this result to others to find the winner etc).
I made classes for an event (with list of event results) , participant (with list of participant's total results), and result (with event obj, participant obj and result (double)) + a program class for the commands.
So far I have written a method that returns a participant's best result in an event as a double, but I need to return the entire result object instead. Not sure which class should be responsible for this (?) but I put this method in the event class for now.
public double getBestResultDouble(Participant p) {
double best = 0;
if (hasResult(p)) {
for (Result r : eventResults) {
if (p.equals(r.getParticipant())) {
double res = r.getResult();
if (res > best) {
best = res;
}
}
}
}
return best;
}
Simply save the corresponding (best) result as another local variable and return that at the end:
public Result getBestResultDouble(Participant p) {
double best = 0;
Result bestResult = null;
if (hasResult(p)) {
for (Result r : eventResults) {
if (p.equals(r.getParticipant())) {
double res = r.getResult();
if (res > best) {
best = res;
bestResult = r;
}
}
}
}
return bestResult;
}
Try to think a little more in terms of OOP (Object-Oriented Programming).
A Participant can participate in multiple Event's. Everytime a Participant participates in an Event, there is a Result that gets generated for that particular Participant and gets a corresponding score.
Now that I have the basic structure of how I want to layout my classes, so we can define specific behavior of how we want each class to go about.
public class Participant
{
private List<Event> events;
private String name;
public Participant( String name )
{
this.name = name;
events = new ArrayList<Event>();
}
//... other methods
public void participate( Event event )
{
double score;
//... get the score
events.add( event );
event.recordResult( this, score );
}
}
So now the Participant can participate in certain Event's that you define.
Now looking at what I did in that participate method, we need to have a way to store Result's inside of an Event.
public class Event
{
private List<Result> results;
public Event()
{
results = new ArrayList<Result>();
}
// ...
public void scoreResult( Participant participant, double score )
{
//if a participant cannot participate multiple times in an event, probably need a check to see if
//the participant is already in the list, otherwise update the score isntead of adding a new Result
results.add( new Result( participant, score ) );
}
}
So now each Event that you define has a way to score a Result for a particular Participant.
I'll just do the basic outline for a Result class
public class Result
{
private Participant participant;
private double score;
public Result( Participant participant, double score )
{
this.participant = participant;
this.score = score;
}
// ... getters and setters
}
This could be a basic outline for your Objects. Now you can define a method in Participant to get the best Result of all Event's he has participated in. Something like:
public Result getBestResult()
{
Result bestResult = null;
for( Event event : events )
{
if( bestResult == null )
bestResult = event.getResult();
if( bestResult.getScore() < event.getResult().getScore() )
bestResult = event.getResult();
}
//if the participant hasn't participated in any event, then this will return a null result.
return bestResult;
}
I hope that this makes sense and will help you think about things in a more OO way. This way you can define other methods for Event such as how to get the Participant with the highest Result.
Of course this is just one way to layout the classes, and I highly encourage you to try to think ahead of time how you want your objects to interact with each other before coding. Think about relationships between objects. I know you are just a beginner, so this might be a little advanced, but if you are serious about learning how to code efficiently, this is definitely something to look into.
In Java, the most common way is to create an instance of Comparator<Result>, which compares instances of Result. You delegate the comparison to the Comparator instead of comparing yourself.
You can then use standard Java methods to do your sorting.
Standard:
List<Result> = ....
Comparator<Result> cmp = ....;
list.sort(cmp);
Result max = list.get(0);
With Streams
List<Result> = ....
Comparator<Result> cmp = ....;
Result max = list.stream().max(cmp);
Double is already Comparable, so you can code your Comparator like:
Comparator<Result> cmp = new Comparator() {
public int compare(Result r1, Result r2)
{
return r1.getResult().compareTo(r2.getResult());
}
}
class Stock{
double profit;
double profitPercentage;
public double getProfit(){
return profit;
}
public double getProfitPercentage(){
return profitPercentage;
}
}
List<Stock> stocks = getAllStocks();
stocks.stream.collect(Collectors.summarizingDouble(Stock:getProfit)).getSum();
stocks.stream.collect(Collectors.summarizingDouble(Stock:getProfitPercentage)).getSum();
I could not find out way to do in single pass of stream. Any help or pointer would be good.
The straight-forward way is to create a custom collector class.
public class StockStatistics {
private DoubleSummaryStatistics profitStat = new DoubleSummaryStatistics();
private DoubleSummaryStatistics profitPercentageStat = new DoubleSummaryStatistics();
public void accept(Stock stock) {
profitStat.accept(stock.getProfit());
profitPercentageStat.accept(stock.getProfitPercentage());
}
public StockStatistics combine(StockStatistics other) {
profitStat.combine(other.profitStat);
profitPercentageStat.combine(other.profitPercentageStat);
return this;
}
public static Collector<Stock, ?, StockStatistics> collector() {
return Collector.of(StockStatistics::new, StockStatistics::accept, StockStatistics::combine);
}
public DoubleSummaryStatistics getProfitStat() {
return profitStat;
}
public DoubleSummaryStatistics getProfitPercentageStat() {
return profitPercentageStat;
}
}
This class serves as a wrapper around two DoubleSummaryStatistics. It delegates to them each time an element is accepted. In your case, since you're only interested in the sum, you could even use a Collectors.summingDouble instead of DoubleSummaryStatistics. Also, it returns the two statistics with getProfitStat and getProfitPercentageStat; alternatively, you could add a finisher operation that would return a double[] containing only both sums.
Then, you can use
StockStatistics stats = stocks.stream().collect(StockStatistics.collector());
System.out.println(stats.getProfitStat().getSum());
System.out.println(stats.getProfitPercentageStat().getSum());
A more generic way is to create a collector capable of pairing other collectors. You can use the pairing collector written in this answer and, also available in the StreamEx library.
double[] sums = stocks.stream().collect(MoreCollectors.pairing(
Collectors.summingDouble(Stock::getProfit),
Collectors.summingDouble(Stock::getProfitPercentage),
(sum1, sum2) -> new double[] { sum1, sum2 }
));
The sum of the profit will be in sums[0] and the sum of the profit percentage will be in sums[1]. In this snippet, only the sums are kept and not the whole stats.
Modifying a local variable in forEach gives a compile error:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
With Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Any idea how to resolve this?
Use a wrapper
Any kind of wrapper is good.
With Java 10+, use this construct as it's very easy to setup:
var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
s.setOrdinal(wrapper.ordinal++);
});
With Java 8+, use either an AtomicInteger:
AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
s.setOrdinal(ordinal.getAndIncrement());
});
... or an array:
int[] ordinal = { 0 };
list.forEach(s -> {
s.setOrdinal(ordinal[0]++);
});
Note: be very careful if you use a parallel stream. You might not end up with the expected result. Other solutions like Stuart's might be more adapted for those cases.
For types other than int
Of course, this is still valid for types other than int.
For instance, with Java 10+:
var wrapper = new Object(){ String value = ""; };
list.forEach(s->{
wrapper.value += "blah";
});
Or if you're stuck with Java 8 or 9, use the same kind of construct as we did above, but with an AtomicReference...
AtomicReference<String> value = new AtomicReference<>("");
list.forEach(s -> {
value.set(value.get() + s);
});
... or an array:
String[] value = { "" };
list.forEach(s-> {
value[0] += s;
});
This is fairly close to an XY problem. That is, the question being asked is essentially how to mutate a captured local variable from a lambda. But the actual task at hand is how to number the elements of a list.
In my experience, upward of 80% of the time there is a question of how to mutate a captured local from within a lambda, there's a better way to proceed. Usually this involves reduction, but in this case the technique of running a stream over the list indexes applies well:
IntStream.range(0, list.size())
.forEach(i -> list.get(i).setOrdinal(i));
If you only need to pass the value from the outside into the lambda, and not get it out, you can do it with a regular anonymous class instead of a lambda:
list.forEach(new Consumer<Example>() {
int ordinal = 0;
public void accept(Example s) {
s.setOrdinal(ordinal);
ordinal++;
}
});
As the used variables from outside the lamda have to be (implicitly) final, you have to use something like AtomicInteger or write your own data structure.
See
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables.
An alternative to AtomicInteger is to use an array (or any other object able to store a value):
final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );
But see the Stuart's answer: there might be a better way to deal with your case.
Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it. Lambdas have been made for functional style of programming and this means: No side effects. What you want to do is considered bad style. It is also dangerous in case of parallel streams.
You should either find a solution without side effects or use a traditional for loop.
If you are on Java 10, you can use var for that:
var ordinal = new Object() { int value; };
list.forEach(s -> {
s.setOrdinal(ordinal.value);
ordinal.value++;
});
You can wrap it up to workaround the compiler but please remember that side effects in lambdas are discouraged.
To quote the javadoc
Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement
A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care
I had a slightly different problem. Instead of incrementing a local variable in the forEach, I needed to assign an object to the local variable.
I solved this by defining a private inner domain class that wraps both the list I want to iterate over (countryList) and the output I hope to get from that list (foundCountry). Then using Java 8 "forEach", I iterate over the list field, and when the object I want is found, I assign that object to the output field. So this assigns a value to a field of the local variable, not changing the local variable itself. I believe that since the local variable itself is not changed, the compiler doesn't complain. I can then use the value that I captured in the output field, outside of the list.
Domain Object:
public class Country {
private int id;
private String countryName;
public Country(int id, String countryName){
this.id = id;
this.countryName = countryName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
}
Wrapper object:
private class CountryFound{
private final List<Country> countryList;
private Country foundCountry;
public CountryFound(List<Country> countryList, Country foundCountry){
this.countryList = countryList;
this.foundCountry = foundCountry;
}
public List<Country> getCountryList() {
return countryList;
}
public void setCountryList(List<Country> countryList) {
this.countryList = countryList;
}
public Country getFoundCountry() {
return foundCountry;
}
public void setFoundCountry(Country foundCountry) {
this.foundCountry = foundCountry;
}
}
Iterate operation:
int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
if(c.getId() == id){
countryFound.setFoundCountry(c);
}
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());
You could remove the wrapper class method "setCountryList()" and make the field "countryList" final, but I did not get compilation errors leaving these details as-is.
To have a more general solution, you can write a generic Wrapper class:
public static class Wrapper<T> {
public T obj;
public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
s.setOrdinal(w.obj);
w.obj++;
});
(this is a variant of the solution given by Almir Campos).
In the specific case this is not a good solution, as Integer is worse than int for your purpose, anyway this solution is more general I think.
the question is the same as in the title. i have arraylist to which i add incomes or expenses both in form of a object. will this loop sum up all elements, and is there a better way of doing this :?
public void sumOfAllExpAndIn(){
int tmp = 0;
for (Iterator<Object> it = database.iterator(); it.hasNext();){
if (it.next() instanceof Expenses){
Expenses excalc = new Expenses();
excalc = (Expenses) it.next();
tmp -= excalc.value;
}
else {
incomes incalc =new incomes();
incalc = (incomes) it.next();
tmp += incalc.value;
}
}
System.out.format("the overall balance is %d",tmp);
}
Yes there are several better ways of doing it.
Firstly, I don't suggest you declare it as an Object list. Better is to declare an interface and then implement the interface in each of your classes:
interface BudgetValue {
double getValue();
}
class Expense implements BudgetValue {
public double getValue() {
return -value;
}
}
class Income implements BudgetValue {
public double getValue() {
return +value;
}
}
Then you can declare list of BudgetValues rather than Objects as the input to your method:
double sumBudgetValues(List<BudgetValues> budgetValues) {
}
There are two easy ways of summing them:
double total = 0.0;
for (BudgetValue value: budgetValues) {
total += value.getValue();
}
return total;
or using Java 8:
return budgetValues.stream()
.mapToDouble(BudgetValue::getValue)
.sum().orElse(0.0);
The streams method makes a lot more sense to me and allows it to be easily multithreaded if you have a lot of values to sum by turning it into a parallel stream.
There are some rare occassions where instanceof is justified but, as a rule of thumb, if you find yourself using it then start by asking yourself whether there's an interface missing.
I suggest making your Expenses and Incomes classes implement a common interface, for example LineItem. Now if you use signed values (positive for incomes and negatives for expenses), you only have to call getValue() on any implementation of LineItem and add it to your running total... no if/else needed, no collection of Object needed.
public void sumOfAllExpAndIn(){
int tmp = 0;
for (Iterator<LineItem> it = database.iterator(); it.hasNext();){
tmp += it.next().getValue();
}
}
System.out.format("the overall balance is %d",tmp);
}