Reputation: 2417
I wanted to write pure function with Java 8 that would take a collection as an argument, apply some change to every object of that collection and return a new collection after the update. I want to follow FP principles so I dont want to update/modify the collection that was passed as an argument.
Is there any way of doing that with Stream API without creating a copy of the original collection first (and then using forEach or 'normal' for loop)?
Sample object below and lets assume that I want to append a text to one of the object property:
public class SampleDTO {
private String text;
}
So I want to do something similar to below, but without modifying the collection. Assuming "list" is a List<SampleDTO>
.
list.forEach(s -> {
s.setText(s.getText()+"xxx");
});
Upvotes: 42
Views: 116541
Reputation: 1870
If you use Lombok, you can use immutable setters.
.map(s -> s.withText(s.getText() + "..."))
See: https://projectlombok.org/features/With
Upvotes: 1
Reputation: 713
I think a cleaner and more readable solution is:
List<SampleDTO> unmodifiable = Arrays.asList(new SampleDTO("1"), new SampleDTO("2"));
List<SampleDTO> modified = unmodifiable.stream()
.map(s -> new SampleDTO(s.getText())) // '.map(SampleDTO::new)' with copy constructor
.peek(s -> s.setText(s.getText() + "xxx"))
.collect(Collectors.toList());
Or if you have copy constructor of SampleDTO you can replace map
function with .map(SampleDTO::new)
.
peek
function will provide action on elements of Stream without changing result type of Stream.
Upvotes: 5
Reputation: 4509
To make this more elegant way I would suggest create a Method
with in the class.
public class SampleDTO {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public SampleDTO(String text) {
this.text = text;
}
public SampleDTO getSampleDTO() {
this.setText(getText()+"xxx");
return this;
}
}
and add it like:
List<SampleDTO> output =list.stream().map(SampleDTO::getSampleDTO).collect(Collectors.toList();
Upvotes: 5
Reputation: 2090
I think it would be better, especially if doing multi-threaded work, to stream the original list into a new modified list or whatever else is desired.
The new list or map or whatever other structure you desire can be created as part of the streaming process.
When the streaming process is completed, simply swap the original with the new.
All of this should occur in a synchronized block.
In this manner, you get the maximum performance and parallelism for the reduce or whatever it is you are doing, and finish with an atomic swap.
Upvotes: 2
Reputation: 1932
Streams are immutable like strings so you cannot get around needing to create a new stream/list/array
That being said you can use .Collect() to return a new collection post change so
List<Integer> result = inList.stream().map().Collect()
Upvotes: 3
Reputation: 393781
You must have some method/constructor that generates a copy of an existing SampleDTO
instance, such as a copy constructor.
Then you can map
each original SampleDTO
instance to a new SampleDTO
instance, and collect
them into a new List
:
List<SampleDTO> output =
list.stream()
.map(s-> {
SampleDTO n = new SampleDTO(s); // create new instance
n.setText(n.getText()+"xxx"); // mutate its state
return n; // return mutated instance
})
.collect(Collectors.toList());
Upvotes: 71