2

I'm wondering if there is a way I can update two times an object in a Stream lambda code, I need to update two properties of a class, I need to update the value and the recordsCount properties

Object:

public class HistoricDataModelParsed {

private Date   startDate;
private Date   endDate;
private Double value;
private int    recordsCount;

}

I tried doing something like this:

val existingRecord = response.stream()
     .filter(dateTime ->fromDate.equals(dateTime.getStartDate()))
     .findAny()
     .orElse(null);


response.stream()
     .filter(dateTime ->fromDate.equals(dateTime.getStartDate()))
     .findAny()
     .orElse(existingRecord)
     .setValue(valueAdded)
     .setRecordsCount(amount);

But I got this error: "Cannot invoke setRecordsCount(int) on the primitive type void"

So I ended up doing the stream two times to update each of the two fields I needed

response.stream()
     .filter(dateTime ->fromDate.equals(dateTime.getStartDate()))
     .findAny()
     .orElse(existingRecord)
     .setValue(valueAdded);
                
 response.stream()
     .filter(dateTime ->fromDate.equals(dateTime.getStartDate()))
     .findAny()
     .orElse(existingRecord)
     .setRecordsCount(amount);      

Is there a way I can achieve what I need without the need to stream two times the list?

xThunder
  • 35
  • 7

3 Answers3

2

The state of an object should not change within a stream. It can lead to inconsistent results. But you can create new instances of the objects and pass new values via the constructor. Here is a simple record that demonstrates the method. Records are basically immutable classes that have no setters. The getters are the names of the variables. A class would also work in this example.

record Temp(int getA, int getB) {
    @Override
    public String toString() {
        return "[" + getA + ", " + getB  +"]";
    }
}

Some data

    List<Temp> list = List.of(new Temp(10, 20), new Temp(50, 200),
        new Temp(100, 200));

And the transformation. A new instance of Temp with new values is created along with the old ones to completely populate the constructor. Otherwise, the existing object is passed along.

List<Temp> result = list.stream().map(
        t -> t.getA() == 50 ? new Temp(2000, t.getB()) : t)
        .toList();

System.out.println(result);

Prints

[[10, 20], [2000, 200], [100, 200]]

To answer the void error you got it's because a stream expects values to continue thru out the stream so if a method is void, it isn't returning anything so you would have to return it. Here is an example:

stream.map(t->{voidReturnMethod(t); return t;}).toList();

The return ensures the pipeline continues.

WJS
  • 36,363
  • 4
  • 24
  • 39
2

Simply store the result of orElse and then call your methods on it.

HistoricDataModelParsed record = 
    response.stream()
        .filter(dateTime -> fromDate.equals(dateTime.getStartDate()))
        .findAny()
        .orElse(existingRecord);

record.setValue(valueAdded)
record.setRecordsCount(amount);
Sebastian
  • 5,177
  • 4
  • 30
  • 47
2

The return type of setValue is void and not HistoricDataModelParsed. So you cannot invoke the method setRecordsCount which is in HistoricDataModelParsed class.

You could have added a method in HistoricDataModelParsed which takes two parameters for value and recordsCount:

public void setValueAndCount(Double value, int count) {
    this.value = value;
    this.recordsCount = count;
}

Then call this method after orElse:

response.stream()
     .filter(dateTime ->fromDate.equals(dateTime.getStartDate()))
     .findAny()
     .orElse(existingRecord)
     .setValueAndCount(valueAdded, amount);
Gautham M
  • 4,816
  • 3
  • 15
  • 37