Zyga
Zyga

Reputation: 2417

Java 8 modify stream elements

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

Answers (6)

Nicolas
Nicolas

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

Alex
Alex

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

soorapadman
soorapadman

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

Rodney P. Barbati
Rodney P. Barbati

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

Theresa Forster
Theresa Forster

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

Eran
Eran

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

Related Questions