WARNING
As mentioned in comments, Using peek() for production code is considered bad practice
The reasson is that "According to its JavaDocs, the intermediate Stream operation java.util.Stream.peek() “exists mainly to support debugging” purposes."
As a consequence, this proposed solution SHOULD NOT be used.
Forgot to relate to the first code snippet. I wouldn't use forEach at all. Since you are collecting the elements of the Stream into a List, it would make more sense to end the Stream processing with collect. Then you would need peek in order to set the ID.
List<Entry> updatedEntries =
entryList.stream()
.peek(e -> e.setTempId(tempId))
.collect (Collectors.toList());
For the second snippet, forEach can execute multiple expressions, just like any lambda expression can :
entryList.forEach(entry -> {
if(entry.getA() == null){
printA();
}
if(entry.getB() == null){
printB();
}
if(entry.getC() == null){
printC();
}
});
However (looking at your commented attempt), you can't use filter in this scenario, since you will only process some of the entries (for example, the entries for which entry.getA() == null) if you do.
WARNING
As mentioned in comments, Using peek() for production code is considered bad practice
The reasson is that "According to its JavaDocs, the intermediate Stream operation java.util.Stream.peek() “exists mainly to support debugging” purposes."
As a consequence, this proposed solution SHOULD NOT be used.
Forgot to relate to the first code snippet. I wouldn't use forEach at all. Since you are collecting the elements of the Stream into a List, it would make more sense to end the Stream processing with collect. Then you would need peek in order to set the ID.
List<Entry> updatedEntries =
entryList.stream()
.peek(e -> e.setTempId(tempId))
.collect (Collectors.toList());
For the second snippet, forEach can execute multiple expressions, just like any lambda expression can :
entryList.forEach(entry -> {
if(entry.getA() == null){
printA();
}
if(entry.getB() == null){
printB();
}
if(entry.getC() == null){
printC();
}
});
However (looking at your commented attempt), you can't use filter in this scenario, since you will only process some of the entries (for example, the entries for which entry.getA() == null) if you do.
List<String> items = new ArrayList<>();
items.add("A");
items.add("B");
items.add("C");
items.add("D");
items.add("E");
//lambda
//Output : A,B,C,D,E
items.forEach(item->System.out.println(item));
//Output : C
items.forEach(item->{
System.out.println(item);
System.out.println(item.toLowerCase());
}
});
lambda - More then 1 command in the Java 8 foreach function - Stack Overflow
Java 8 Lambda Stream forEach with two if-statements - Stack Overflow
java - multiple lines of code in stream.forEach - Stack Overflow
Java 8 Lambda Stream forEach with multiple statements
Can we use forEach in streams Java?
Is stream forEach better than for-loop?
The lambda syntax allows two kinds of definitions for the body:
- a single, value-returning, expression, eg:
x -> x*2 - multiple statements, enclosed in curly braces, eg:
x -> { x *= 2; return x; }
A third special case is the one that allows you to avoid using curly braces, when invoking a void returning method, eg: x -> System.out.println(x).
Use this:
map.forEach(
(k,v) -> {
System.out.println(k);
v.forEach(t->System.out.print(t.getDescription()))
}
);
There are two completely different things you should ask here:
a) how do I place multiple lines of code in stream.forEach()?
b) what should I do to count the number of lines in a Stream?
The question b) is answered already by other posters; on the other hand, the general question a) has a quite different answer:
use a (possibly multi-line) lambda expression or pass a reference to multi-line method.
In this particular case, you'd either declare i a field or use a counter/wrapper object instead of i.
For example, if you want to have multiple lines in forEach() explicitly, you can use
class Counter { // wrapper class
private int count;
public int getCount() { return count; }
public void increaseCount() { count++; }
}
and then
Counter counter = new Counter();
lines.stream().forEach( e -> {
System.out.println(e);
counter.increaseCounter(); // or i++; if you decided i is worth being a field
} );
Another way to do it, this time hiding those multiple lines in a method:
class Counter { // wrapper class
private int count;
public int getCount() { return count; }
public void increaseCount( Object o ) {
System.out.println(o);
count++;
}
}
and then
Counter counter = new Counter();
lines.stream().forEach( counter::increaseCount );
or even
Counter counter = new Counter();
lines.stream().forEach( e -> counter.increaseCount(e) );
The second syntax comes in handy if you need a consumer having more than one parameter; the first syntax is still the shortest and simplest though.
The forEach method takes an instance of any class that implements Consumer. So here is an example of using a custom Consumer implementation that keeps up with the count. Later you can call getCount() on the Consumer implementation to get the count.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerDemo {
public static void main(String[] args) {
List<String> lines = new ArrayList<String>();
lines.add("line 1");
lines.add("line 2");
MyConsumer countingConsumer = new MyConsumer();
lines.stream().forEach(countingConsumer);
System.out.println("Count: " + countingConsumer.getCount());
}
private static class MyConsumer implements Consumer<String> {
private int count;
@Override
public void accept(String t) {
System.out.println(t);
count++;
}
public int getCount() {
return count;
}
}
}
It's fairly deeply nested but it doesn't seem exceptionally difficult.
The first observation is that if a for-loop translates into a stream, nested for-loops can be "flattened" into a single stream using flatMap. This operation takes a single element and returns an arbitrary number elements in a stream. I looked up and found that StandardServer.findServices() returns an array of Service so we turn this into a stream using Arrays.stream(). (I make similar assumptions for Engine.findChildren() and Host.findChildren().
Next, the logic within each loop does an instanceof check and a cast. This can be modeled using streams as a filter operation to do the instanceof followed by a map operation that simply casts and returns the same reference. This is actually a no-op but it lets the static typing system convert a Stream<Container> to a Stream<Host> for example.
Applying these transformations to the nested loops, we get the following:
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
But wait, there's more.
The final forEach operation is a slightly more complicated map operation that converts a Context into a ContextInfo. Furthermore, these are just collected into a List so we can use collectors to do this instead of creating and empty list up front and then populating it. Applying these refactorings results in the following:
public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
I usually try to avoid multi-line lambdas (such as in the final map operation) so I'd refactor it into a little helper method that takes a Context and returns a ContextInfo. This doesn't shorten the code at all, but I think it does make it clearer.
UPDATE
But wait, there's still more.
Let's extract the call to service.getContainer() into its own pipeline element:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
This exposes the repetition of filtering on instanceof followed by a mapping with a cast. This is done three times in total. It seems likely that other code is going to need to do similar things, so it would be nice to extract this bit of logic into a helper method. The problem is that filter can change the number of elements in the stream (dropping ones that don't match) but it can't change their types. And map can change the types of elements, but it can't change their number. Can something change both the number and types? Yes, it's our old friend flatMap again! So our helper method needs to take an element and return a stream of elements of a different type. That return stream will contain a single casted element (if it matches) or it will be empty (if it doesn't match). The helper function would look like this:
<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
(This is loosely based on C#'s OfType construct mentioned in some of the comments.)
While we're at it, let's extract a method to create a ContextInfo:
ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
After these extractions, the pipeline looks like this:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
Nicer, I think, and we've removed the dreaded multi-line statement lambda.
UPDATE: BONUS CHALLENGE
Once again, flatMap is your friend. Take the tail of the stream and migrate it into the last flatMap before the tail. That way the host variable is still in scope, and you can pass it to a makeContextInfo helper method that's been modified to take host as well.
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, host)))
.collect(Collectors.toList());
This would be my version of your code using JDK 8 streams, method references, and lambda expressions:
server.findServices()
.stream()
.map(Service::getContainer)
.filter(Engine.class::isInstance)
.map(Engine.class::cast)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(Host.class::isInstance)
.map(Host.class::cast)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(Context.class::isInstance)
.map(Context.class::cast)
.map(context -> {
ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
In this approach, I replace your if-statements for filter predicates. Take into account that an instanceof check can be replaced with a Predicate<T>
Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
which can also be expressed as
Predicate<Object> isEngine = Engine.class::isInstance
Similarly, your casts can be replaced by Function<T,R>.
Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
Which is pretty much the same as
Function<Object,Engine> castToEngine = Engine.class::cast;
And adding items manually to a list in the for loop can be replaced with a collector. In production code, the lambda that transforms a Context into a ContextInfo can (and should) be extracted into a separate method, and used as a method reference.