Java 8 apply function to all elements of Stream without breaking stream chain
Asked Answered
R

7

73

Is there a way in Java to apply a function to all the elements of a Stream without breaking the Stream chain? I know I can call forEach, but that method returns a void, not a Stream.

Reganregard answered 5/5, 2017 at 23:52 Comment(6)
Does the method you want to invoke return a value?Marashio
can you explain what you mean by without breaking the stream chain? may be an example?Pocahontas
Alex means something like my made-up apply method here: things.stream().apply(o -> o.status = newStatus).map(....).... Same idea as forEach, but does not "break the chain", e.g., returns a Stream<T> instead of void.Dys
For example. I have some filters applied to a stream, then apply some setters to fields of its elements, and then do other stuff, like more filters or mapping, or collecting. Or I have several consumers, which I don't want to pack in one function. But I can't use forEach, cause it doesn't return the stream, and have to add "return", which I don't need if I use a map for this. List<SomeType> list = requestEntityList.stream() .filter(Objects::nonNull) .filter(e -> sessionEntity.getId().equals(e.getiSession())) .map(e -> {e.setErrMsg(null); return e;}) .collect(Collectors.toList());Pugliese
@JoshM. there is no method such as stream().apply , did you try ? Can you provide a working example.Dirham
@Dirham I know there's not (I said I made it up in my previous comment) ... but there should be! You can use stream().map(...) to do the same thing but it requires you to return the same item back in the lambda, which is silly.Dys
W
92

There are (at least) 3 ways. For the sake of example code, I've assumed you want to call 2 consumer methods methodA and methodB:

A. Use peek():

list.stream().peek(x -> methodA(x)).forEach(x -> methodB(x));

Although the docs say only use it for "debug", it works (and it's in production right now)

B. Use map() to call methodA, then return the element back to the stream:

list.stream().map(x -> {method1(x); return x;}).forEach(x -> methodB(x));

This is probably the most "acceptable" approach.

C. Do two things in the forEach():

list.stream().forEach(x -> {method1(x); methodB(x);});

This is the least flexible and may not suit your need.

Winterfeed answered 6/5, 2017 at 11:17 Comment(15)
The implications of performing an action with side effects in map’s function and using peek for such a (non-debug) side effect, are basically the same.Prosthetics
@Prosthetics who said anything about side effects?Winterfeed
The invocation of method1(x); would be entirely useless, if it hadn’t side effects.Prosthetics
@holger incorrect. Method1 may do something with it, without mutation. I have actually used this exact pattern (the peek() version) when I wanted to collect the elements (to a Set, not timing critical, ie no problem) partway through a stream but also send the tail of the stream to a consumer via forEach(). Not useless.Winterfeed
And what do you mean by “do something with it”, other than producing a side effect? What do you think, does “side effect” mean, if modifying a Set was not a side effect?Prosthetics
@holger A "side effect" a state change that is unadvertised and/or unexpected. Obviously a Set is mutated by adding to it, but that's expected, so it's not a "side effect". If the consumer mutated the element without advertising it, that would be a side effect. You seem tot be saying that if "something changes" when you invoke a method then it has a side effect, but that is nonsense. If a method is supposed to change something, then it's not a side effect; it's expected behavior.Winterfeed
Your definition of “side effect” does not match the definition of the rest of the IT world. That’s especially dangerous, as API documentations, like that of the Stream API, discouraging side effects in functions, are referring to the standard definition, e.g. Wikipedia: “In computer science, a function or expression is said to have a side effect if it modifies some state outside its scope or has an observable interaction with its calling functions or the outside world”. It is irrelevant whether it is intentional or unexpected.Prosthetics
Option C, forEach, does not return a stream so you cannot continue to work on the stream.Dys
@Josh That's not relevant: You need a terminating method (like forEach) to do make anything happen, and this question is about calling mutliple consumers in the stream. Option C is one way to do this.Winterfeed
@Winterfeed "apply function to all elements of Stream without breaking stream chain" -- Stream.forEach specifically accomplishes the first half of the OP's question, but not the second since you cannot continue the "stream chain" after calling forEach, e.g. things.filter(...).forEach(o -> o.setActive(true)).reduce(...).map(...).collect(...) -- forEach returns void, which "ends the stream". I was hoping for an apply(...) function on Stream, which would be similar to forEach but apply things in sequence instead of all at once (as the stream is iterated), and allow continuation.Dys
@JoshM. My code was just an example. You can call peek() twice if you need the stream after the two operations: list.stream().peek(x -> methodA(x)).peek(x -> methodB(x))... or once with both operations: list.stream().peek(x -> {methodA(x); methodB(x);})...Winterfeed
@Boheamian Don't use peek, map is what you must use, peek does not mandate processing of all elements, it depends on what the terminal operation needs and it does not guarantee same order of processing, that's why documentation explicitly says "This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline"Javierjavler
@Javierjavler and yet it works, my code passes all tests and the use of peek() in my applications written for very large organisations has been running flawlessly in production for many years.Winterfeed
@Winterfeed "It works in my computer" is not a good argument. Your use case maybe doesn't depend on ordering and execution for all elements, but you don't know the use case of OP or people looking for the correct answer. You will face problems under heavy parallelization or when another developer consume your stream and doesn't know that your implementation can skip elements depending on the terminal operation he uses. I hope those large organisations don't look at your code.Javierjavler
@Javierjavler I have high parallelisation, but not everyone cares about execution order. In fact, caring about execution order is a design smell. I'm proud of my code. It was so good in fact that a large body of 3rd party software was kicked to the curb. Anyway, the key to safely using what you claim is unsafe is to have copious automated testing, which of course I have. If there's no test that can be written that your code won't pass, your code is safe. It's an empirical proof rather than a theoretical one. Physics works the same way. Theorists theorise, but experimenters prove.Winterfeed
H
6

You are looking for the Stream's map() function.

example:

List<String> strings = stream
.map(Object::toString)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Hydromancy answered 5/5, 2017 at 23:59 Comment(3)
If I use a map, I'll have to use multiple lines. something like myStream.map(obj -> {obj.foo(); return obj;}). I was wondering if there is a one line solution to that.Reganregard
You can also refer a method there, check my mod.Hydromancy
Map is not the correct place to take action on the element of the stream, it is for "converting" an item to something else.Dys
M
2

The best option you have is to apply the map to your stream. which returns a stream consisting of the results of applying the given function to the elements of the stream. For example:

IntStream.range(1, 100)
           .boxed()
           .map(item->item+3)
           .map(item->item*2)... 

We are applying several modifications to the stream but in some case we don't want to modify the stream. We just want to visit every element and then pass it down the stream without modification (like the peek() method in the streams API). in such cases, we can

StreamItem peekyMethod(StreamItem streamItemX) {
   // .... visit the streamItemX
   //Then pass it down the stream
   return streamItemX;
}
Mesocratic answered 1/7, 2017 at 9:4 Comment(0)
P
1

Not entirely sure what you mean by breaking the stream chain, but any operation on a Stream that returns a Stream will not break or consume your Stream. Streams are consumed by terminal operations and as you noted the forEach does not return a Stream<T> and as such ends the stream, by executing all the intermediate operations before the forEach and the forEach itself.

In the example that you provided in the comments:

 myStream.map(obj -> {obj.foo(); return obj;}

You can't really do this with one liner. Of course you could use a method reference, but then your returned Stream would be of a different type (assuming foo returns a type):

  myStream.map(Obj::foo) // this will turn into Stream<T>, where T is 
           // the return type of foo, instead of Stream<Obj>

Besides that your map operation is stateful, which is strongly discouraged. Your code will compile and might even work as you want it to - but it might later fail. map operations should be stateless.

Pocahontas answered 6/5, 2017 at 6:11 Comment(0)
M
1

You can use map method but you have to create helper method which returns this. For example:

public class Fluent {
  public static <T> Function<T, T> of(Consumer<T> consumer) {
    return t -> {
      consumer.accept(t);
      return t;
   };
 }
}

And use it when you want to call void method:

list.stream().map(Fluent.of(SomeClass::method));

or if you want to use it with method with some argument:

list.stream().map(Fluent.of(x -> x.method("hello")))
Magistracy answered 7/11, 2019 at 7:43 Comment(0)
M
0

I think you are looking for Stream.peek. But read the docs carefully, as it was designed mainly as a debug method. From the docs:

This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline

The action passed to peek must be non interfering.

Marashio answered 6/5, 2017 at 3:12 Comment(0)
N
0

I think the cleanest way is to add a mutator to the objects in the stream.

For example,

class Victim {
   private String tag;
   private Victim withTag(String t)
      this.tag = t;
      return this;
   }
}

List<Victim> base = List.of(new Victim());
Stream<Victim> transformed = base.stream().map(v -> v.withTag("myTag"));

If you prefer (and many will), you can have the withTag method create and return a new Victim; this allows you to make Victim immutable.

Norine answered 15/8, 2019 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.