Convert a For loop to a lambda expression in Java - java

I have the following code
//assume we have a list of custom type "details" already constructed
for(int i = 0; i < details.size(); ++i) {
CallerID number = details.get(i).getNextNumber();
ClientData.addToClient(number);
}
I have oversimplified the code. The enum CallerID and the ClientData object work as intended. I am asking for help converting this loop to a lambda function so I can understand the logic of how to do so, then fill in the appropriate code as needed.

Let's first write it as a modern basic for loop and golf it a bit, just so we're comparing apples to apples:
for (var detail : details) clientData.addToClient(detail.getNextNumber());
And this is probably the right answer. It is local var, exception, and control flow transparent (which is what you want), and short.
The lambda form is this, but it's got downsides (mostly, those transparencies). It also isn't any shorter. You shouldn't write it this way.
details.stream().forEach(d -> clientData.addToClient(detail.getNextNumber());
You may be able to just remove stream() from that. But probably not.
Generally when people say "I want it in lambda form", that's not because someone is holding a gun to your head - you are saying that because somebody peddling a religion of sorts to you told you that 'it was better' and that this 'will scale'. Realize that they are full of it. There can be advantages to 'functional style', but none of these snippets are functional. A true functional style would involve a bunch of side-effect-free transformations, and then returning something.
.addToClient? You've lost the functional game there - you would want to instead convert each detail to something (presumably a ClientID), and from there construct an immutable object from that stream. You'd 'collect' your ClientIDs into a clientData object.
Let's say for example that clientData is just a 'list of ClientIDs' and nothing more. Then you'd write something like this:
var clientData = details.stream()
.map(MyDetailClass::getNextNumber)
.collect(Collectors.toList());
Is this better? No. However, if you're looking for 'a stream-style, lambda-based functional take on things', that qualifies. The output is constructed by way of collection (and not forEach that does a side-effect operation), and all elements involved are (or can be) immutable.
There's no particular reason why you'd want this, but if for some reason you're convinced this is better, now you know what you want to do. "Just replace it with a lambda" doesn't make it 'functional'.

I am asking for help converting this loop to a lambda function so I can understand the logic of how to do so, then fill in the appropriate code as needed.
A Function returns a value. As you are just updating something what you need is a Consumer which accepts a single argument of a list of some detail. Assuming those are in a Class named SomeDetails, here is how you would do it.
As you iterating over some structure limited by size and using get(i) I am presuming a list is required here.
List<SomeDetails> details = new ArrayList<>(); // then populated
// lambda definition
Consumer<List<SomeDetails>> update = (lst)-> {
for(SomeDetails detail : lst) {
CallerID number = detail.getNextNumber();
ClientData.addToClient(number);
}
};
And then invoke it like this, passing the List.
update.accept(details);
All the above does is encapsulate the for loop (using the enhanced version for simplicity) and perform the operation.
If this is all you wanted, I would recommend just doing it as you were doing it sans the lambda.

Related

Transform list of Either into list of left and list of right

Vavr's Either seems to solve one of my problems were some method does a lot of checks and returns either CalculationError or CalculationResult.
Either<CalculationError, CalculationResult> calculate (CalculationData calculationData) {
// either returns Either.left(new CalculationError()) or Either.right(new CalculationResult())
}
I have a wrapper which stores both errors and results
class Calculation {
List<CalculationResult> calculationResults;
List<CalculationError> calculationErrors;
}
Is there any neat solution to transform stream from Collection<CalculationData> data to Calculation?
This can be easily done using a custom collector. With a bit of pseudo code representing the Either:
Collector<Either<CalculationError, CalculationResult>, ?, Calculation> collector = Collector.of(
Calculation::new,
(calc, either) -> {
if (either has error) {
calc.calculationErrors.add(either.error);
} else {
calc.calculationResults.add(either.result);
}
},
(calc1, calc2) -> {
calc1.calculationErrors.addAll(calc2.calculationErrors);
calc1.calculationResults.addAll(calc2.calculationResults);
return calc1;
}
);
Calculation calc = data.stream()
.map(this::calculate)
.collect(collector);
Note that Calculation should initialize its two lists (in the declaration or a new constructor).
Well, you're using vavr, so 'neat' is right out. Tends to happen when you use tools that are hostile to the idiomatic form of the language. But, then again, 'neat' is a nebulous term with no clear defined meaning, so, I guess, whatever you think is 'neat', is therefore 'neat'. Neat, huh?
Either itself has the sequence method - but both of them work the way Either is supposed to work: They are left-biased in the sense that any Lefts present is treated as erroneous conditions, and that means all the Right values are discarded if even one of your Eithers is a Left. Thus, you cannot use either of the sequence methods to let Either itself bake you a list of the Right values. Even sequenceRight won't do this for you (it stops on the first Left in the list and returns that instead). The filter stuff similarly doesn't work like that - Either very much isn't really an Either in the sense of what that word means if you open a dictionary: It does not mean: A homogenous mix of 2 types. It's solely a non-java-like take on exception management: Right contains the 'answer', left contains the 'error' (you're using it correctly), but as a consequence there's nothing in the Either API to help with this task - which in effect involves 'please filter out the errors and then do something' ("Silently ignore errors" is rarely the right move. It is what is needed here, but it makes sense that the Either API isn't going to hand you a footgun. Even if you need it here).
Thus, we just write it plain jane java:
var calculation = new Calculation();
for (var e : mix) {
if (e.isLeft()) calculation.calculationErrors.add(e.getLeft());
if (e.isRight()) calculation.calculationResult.add(e.getRight());
}
(This presumes your Calculation constructor at least initializes those lists to empty mutables).
NB: Rob Spoor's answer also assumes this and is much, much longer. Sometimes the functional way is the silly, slow, unwieldy, hard to read, way.
NB2: Either.sequence(mix).orElseRun(s -> calculation.errors = s.asJava()); is a rather 'neat' way (perhaps - it's in the eye of the beholder) of setting up the errors field of your Calculation class. No joy for such a 'neat' trick to fill the 'results' part of it all, however. That's what the bulk of my answer is trying to explain: There is no nice API for that in Either, and it's probably by design, as that involves intentionally ignoring the errors in the list of Eithers.
Since you are using VAVr, you may consider using Traversable instead of Collection. This will give you the method partition, which can be used to classify your list of Eithers into two groups like so:
Traversable<Either<CalculationError, CalculationResult>> calculations = ...;
var partitionedCalcs = calculations.partition(Either::isRight);
var results = partitionedCalcs._1.map(Either::getRight);
var errors = partitionedCalcs._2.map(Either::getLeft);
Calculation calcs = new Calculation(results, errors);
If you don't want to change your existing use of Collection to use a Traversable, then you can easily convert between them by using, for example, List.ofAll(Iterator) and Value.toJavaCollection(Function).

Does it lead to data corruption when we access an outside object in a Java 8 map function?

I have object customerSummary at line #2 and accessing it at lines #11 & #12. Does it lead to data corruption in production?
private CustomerSummary enrichCustomerIdentifiers(CustomerSummaryDTO customerSummaryDTO) {
CustomerSummary customerSummary = customerSummaryDTO.getCustomerSummary();
List<CustomerIdentifier> customerIdentifiers = customerSummary
.getCustomerIdentifiers().stream()
.peek(customerIdentifier -> {
if (getCustomerReferenceTypes().contains(customerIdentifier.getIdentifierType())) {
customerIdentifier.setRefType(RefType.REF.toString());
} else {
customerIdentifier.setRefType(RefType.TAX.toString());
Country country = new Country();
country.setIsoCountryCode(customerSummary.getCustomerAddresses().get(0).getIsoCountryCode());
country.setCountryName(customerSummary.getCustomerAddresses().get(0).getCountryName());
customerIdentifier.setCountry(country);
}
}).collect(Collectors.toList());
customerSummary.setCustomerIdentifiers(customerIdentifiers);
return customerSummary;
}
The literal answer to your question is No ... assuming that the access is thread-safe.
But your code probably doesn't do what you think it does.
The peek() method returns the precise stream of objects that it is called on. So your code is effectively doing this:
summary.setCustomerIdentifiers(
new SomeListClass<>(summary.getCustomerIdentifiers()));
... while doing some operations on the identifier objects.
You are (AFAIK unnecessarily) copying the list and reassigning it to the field of the summary object.
It would be simpler AND more efficient to write it as:
for (CustomerIdentifier id: summary.getCustomerIdentifiers()) {
if (getCustomerReferenceTypes().contains(id.getIdentifierType())) {
id.setRefType(RefType.REF.toString());
} else {
id.setRefType(RefType.TAX.toString());
Country country = new Country();
Address address = summary.getCustomerAddresses().get(0);
country.setIsoCountryCode(address.getIsoCountryCode());
country.setCountryName(address.getCountryName());
id.setCountry(country);
}
}
You could do the above using a list.stream().forEach(), or a list.forEach(), but the code is (IMO) neither simpler or substantially more concise than a plain loop.
summary.getCustomerIdentifiers().forEach(
id -> {
if (getCustomerReferenceTypes().contains(id.getIdentifierType())) {
id.setRefType(RefType.REF.toString());
} else {
id.setRefType(RefType.TAX.toString());
Country country = new Country();
Address address = summary.getCustomerAddresses().get(0);
country.setIsoCountryCode(address.getIsoCountryCode());
country.setCountryName(address.getCountryName());
id.setCountry(country);
}
}
);
(A final micro-optimization would be to declare and initialize address outside of the loop.)
Java 8 streams are not the solution to all problems.
The direct answer to your question is a resounding 'no', but you're misusing streams, which presumably is part of why you are even asking this question. You're operating on mutables in stream code, which you shouldn't be doing: It's why I'm saying 'misusing' - this code compiles and works but leads to hard to read and had to maintain code that will fail in weird ways as you use more and more of the stream API. The solution is not to go against the grain so much.
You're also engaging in stringly based typing which is another style mistake.
Finally, your collect call is misleading.
So, to answer the question:
Does it lead to data corruption in production?
No. How would you imagine it would?
Style mistake #1: mutables
Streams don't work nearly as well when you're working with mutables. The general idea is that you have immutable classes (classes without any setters; the instances of these classes cannot change after construction. String is immutable, so is Integer, and so is BigDecimal. There is no .setValue() on an integer instance, there is no setChar() on a string, or even a clear() or an append() - all operations on immutables that appear to modify things actually return a new instance that contains the result of the operation. someBigDecimal.add() doesn't change what someBigDecimal is pointing at; it constructs a new bigDecimal instance and returns that.
With immutables, if you want to change things, Stream's map method is the right one to use: For example, if you have a stream of BigDecimal objects and you want to, say, print them all, but with 2.5 added to them, you'd be calling map: You want to map each input BigDecimal into an output BD by asking the BD instance to make a new BD instance by adding 2.5 to itself.
With mutables, both map and peek are more relevant. Style debates are rife on what to do. peek just lets you witness what's going through a stream pipeline. It can be misleading because stream pipelines dont process anything until you stick a terminator on the end (something like collect, or max() or whatnot, those are 'terminators'). When talking about mutables, peek in theory works just as well as map does and some (evidently, including intellij's auto-suggest authors) are of the belief that a map operation that really just mutates the underlying object in the stream and returns the same reference is a style violation and should be replaced with a peek operation instead.
But the far more relevant observation is that stream operations should not be mutating anything at all. Do not call setters.
You have 2 options:
Massively refactor this code, make CustomIdentifier immutable (get rid of the getters, make all fields final, consider adding with-ers and builders and the like), change your peek code to something like:
.map(identifier -> {
if (....) return customerIdentifier.with(RefType.REF);
return identifier.withCountry(new Country(summary.get..., summary.get...));
})
Note that Country also needs this treatment.
Do not use streams.
This is much simpler. This code is vastly less confusing and better style if you just write a foreach loop. I have no idea why you thought streams were appropriate here. Streams are not 'better'. A problem is that adherents of functional style are so incredibly convinced they are correct they spread copious FUD (Fear, Uncertainty, Doubt) about non-functional approaches and strongly insinuate that functional style is 'just better'. This is not true - it's merely a different style that is more suitable to some domains and less to others. This style goes a lot further than just 'turn for loops into streams', and unawareness of what 'functional style' really means just leads to hard to maintain, hard to read, weird code like what you pasted.
I really, really want to use streams here
This is just a bad idea here (unless you do the full rewrite to immutables), but if you MUST, the actual right answer is not what intellij said, it's to use forEach. This is peek and the terminal in one package. It gets rid of the pointless collect (which just recreates a list that is 100% identical to what customerSummary.getCustomerIdentifiers() returns) call and properly represents what is actually happening (which is NOT that you're writing code that witnesses what is flowing through the stream pipe, you're writing code that you intend to execute on each element in the stream).
But that's still much worse than this:
CustomerSummary summary = custumerSummaryDTO.getCustomerSummary();
for (CustomerIdentifier identifier : summary.getCustomerIdentifiers()) {
if (getCustomerReferenceTypes().contains(customerIdentifier.getIdentifierType())) {
customerIdentifier.setRefType(RefType.REF.toString());
} else {
customerIdentifier.setRefType(RefType.TAX.toString());
Country country = new Country();
country.setIsoCountryCode(customerSummary.getCustomerAddresses().get(0).getIsoCountryCode());
country.setCountryName(customerSummary.getCustomerAddresses().get(0).getCountryName());
customerIdentifier.setCountry(country);
}
}
return customerSummary;
Style mistake #2: stringly typing
Why isn't the refType field in CustomerIdentifier just RefType? Why are you converting RefType instances to strings and back?
DB engines support enums and if they don't, the in-between layer (your DTO) should support marshalling enums into strings and back.

Javaslang - how do I join two successful Trys?

I'm still learning Javaslang/Vavr, so excuse the ignorance. The project I'm working on is stuck on Javaslang 2.1.0.
My question: is there a more "Functional" way (as opposed to imperative style) to structure my code to join multiple Trys only once they are successful?
I want to Try each input independently, the idea being to get as much as possible error information - I do not want to stop on the first error encountered (so orElse() etc. won't do the trick). But once no errors are found any more, I want to do something further involving all of the inputs.
My current code looks like this (suitably anonymized):
Try<BigDecimal> amountTry = Try.of(this::readNumber)
.map(BigDecimal::valueOf)
.onFailure(this::collectError);
Try<Currency> currencyTry = Try.of(this::readString)
.map(currency -> currencyLookup(Currency.class, currency))
.onFailure(this::collectError);
if (amountTry.isSuccess() && currencyTry.isSuccess()) {
sale.setAmount(Amount.of(amountTry.get(), currencyTry.get()));
}
Can you suggest a pattern to replace the if() with something more in the functional style of programming?
The Javaslang/Vavr construct that you are looking for is the for comprehension construct, which is accessible through the API.For methods.
import javaslang.control.Try;
import static javaslang.API.For;
...
For(amountTry, currencyTry)
.yield(Amount::of)
.forEach(sale::setAmount);
That is, if both amountTry and currencyTry are non-empty, it creates an Iterable by yielding a result value on the cross-product of the two iterables, and performing an action on each of the resulting elements by invoking a Consumer. Here is the same in lambda form with explicit input types, if it helps you better understand it:
For(amountTry, currencyTry)
.yield((BigDecimal amount, Currency currency) -> Amount.of(amount, currency))
.forEach((Amount amount) -> sale.setAmount(amount));
Later versions of the library have overloads of the for comprehension for Try which will return a Try instance instead of Iterable, which makes the API a little bit nicer if you want to stay in Try domain.

Java Aggregate Operations vs Anonymous class suggestion

In this program, let’s say I have a class Leader that I want to assign to a class Mission. The Mission requires a class Skill, which has a type and a strength. The Leader has a List of Skills. I want to write a method that assigns a Leader (or a number of leaders) to a Mission and check if the Leaders’ combined skill strength is enough to accomplish the Mission.
public void assignLeaderToMission(Mission m, Leader... leaders) {
List<Leader> selectedLeaders = new ArrayList(Arrays.asList(leaders));
int combinedStrength = selectedLeaders
.stream()
.mapToInt(l -> l.getSkills()
.stream()
.filter(s -> s.getType() == m.getSkillRequirement().getType())
.mapToInt(s -> s.getStrength())
.sum())
.sum();
if(m.getSkillRequirement().getStrength() > combinedStrength)
System.out.println("Leader(s) do not meet mission requirements");
else {
// assign leader to mission
}
}
Is this the appropriate way to use a stream with lambda operations? NetBeans is giving a suggestion that I use an anonymous class, but I thought that lambas and aggregate operations were supposed to replace the need for anonymous classes with a single method, or maybe I am interpreting this incorrectly.
In this case, I am accessing a List<> within a List<> and I am not sure this is the correct way to do so. Some help would be much appreciated.
There is nothing wrong with using lambda expressions here. Netbeans just offers that code trans­for­ma­tion, since is is possible (and Netbeans can do the transformation for you). If you accept the offer and let it convert the code, it very likely starts offering converting the anonymous class to a lambda expression as soon as the conversion has been done, simply because it is (now) possible.
But if you want to improve your code, you should not use raw types, i.e. use
List<Leader> selectedLeaders = new ArrayList<>(Arrays.asList(leaders));
instead. But if you just want a List<Leader> without needing support for add or remove, there is no need to copy the list into an ArrayList, so you can use
List<Leader> selectedLeaders = Arrays.asList(leaders);
instead. But if all you want to do, is to stream over an array, you don’t need a List detour at all. You can simply use Arrays.stream(leaders) in the first place.
You may also use flatMap to reduce the amount of nested code, i.e.
int combinedStrength = Arrays.stream(leaders)
.flatMap(l -> l.getSkills().stream())
.filter(s -> s.getType() == m.getSkillRequirement().getType())
.mapToInt(s -> s.getStrength())
.sum();
Lambda must be concise so that it is easy to maintain. If the lambda expression is lengthy, then the code will become hard to maintain and understand. Even debugging will be harder.
More details on Why the perfect lambda expression is just one line can be read here.
The perilously long lambda
To better understand the benefits of writing short, concise lambda expressions, consider the opposite: a sprawling lambda that unfolds over several lines of code:
System.out.println(
values.stream()
.mapToInt(e -> {
int sum = 0;
for(int i = 1; i <= e; i++) {
if(e % i == 0) {
sum += i;
}
}
return sum;
})
.sum());
Even though this code is written in the functional style, it misses the benefits of functional-style programming. Let's consider the reasons why.
1. It's hard to read
Good code should be inviting to read. This code takes mental effort to read: your eyes strain to find the beginning and end of the different parts.
2. Its purpose isn't clear
Good code should read like a story, not like a puzzle. A long, anonymous piece of code like this one hides the details of its purpose, costing the reader time and effort. Wrapping this piece of code into a named function would make it modular, while also bringing out its purpose through the associated name.
3. Poor code quality
Whatever your code does, it's likely that you'll want to reuse it sometime. The logic in this code is embedded within the lambda, which in turn is passed as an argument to another function, mapToInt. If we needed the code elsewhere in our program, we might be tempted to rewrite it, thus introducing inconsistencies in our code base. Alternatively, we might just copy and paste the code. Neither option would result in good code or quality software.
4. It's hard to test
Code always does what was typed and not necessarily what was intended, so it stands that any nontrivial code must be tested. If the code within the lambda expression can't be reached as a unit, it can't be unit tested. You could run integration tests, but that is no substitute for unit testing, especially when that code does significant work.
5. Poor code coverage
Lambdas that were embedded in arguments were not easily extracted as units, and many showed up red on the coverage report. With no insight, the team simply had to assume that those pieces worked.

Idiomatic Collection iteration in Java 8

What is considered idiomatic iteration of a Collection in Java 8, and why?
for (String foo : foos) {
String bar = bars.get(foo);
if (bar != null)
System.out.println(foo);
}
or
foos.forEach(foo -> {
String bar = bars.get(foo);
if (bar != null)
System.out.println(foo);
});
In the comment thread to this answer, user Bringer128 mentioned these questions regarding a similar issue in C#:
foreach vs someList.Foreach(){}
Generic lists: foreach or list.ForEach?
I would caution against applying the C# discussion to Java. The discussion is interesting, to be sure, and the issues are superficially similar. However, Java and C# are different languages and thus different considerations apply.
For example, this answer mentions that the C# foreach statement is preferable, because the compiler might be able to optimize the loop better in the future. This is not true of Java. In Java, the "enhanced for" loop is defined to be syntactic sugar for getting an Iterator and calling its hasNext and next methods repeatedly. This pretty much guarantees a minimum of two method calls per loop iteration (although there is a possibility for the JIT to inline small methods).
Another example is from this answer, which mentions that in C# it is legal for the delegate invoked by a list's ForEach method to modify the list that it's iterating. In Java there is a blanket prohibition of "interference" with the stream source for the Stream.forEach method, whereas for the enhanced-for loop, the behavior of modifying the underlying list (or whatever) is determined by the Iterator. Many are fail-fast and will throw ConcurrentModificationException if the underlying list is modified during iteration. Others will silently give unexpected results.
In any case, don't read the C# discussion and assume that similar reasoning applies to Java.
Now, to answer the question. :-)
I think it's too early to declare one style to be idiomatic or preferable to another at this point. Java 8 has just been released and very few people have much experience with it. Lambdas are new and unfamiliar, and this will make many programmers uncomfortable. They'll thus want to stick to their tried-and-true for-loops. That's perfectly sensible. In a few years, though, after everyone gets used to lambdas, it might be that for-loops will start to look distinctly old-fashioned. Time will tell.
(I think this happened with generics. When they were new, they were intimidating and scary, especially wildcards. Nowadays, though, non-generic code looks distinctly old-fashioned, and to me it has a musty odor about it.)
I have an early sense of how this might turn out. Of course, I might be wrong though.
I'd say that for short loops where the computation is fixed, such as the question posted initially:
for (String foo : foos)
System.out.println(foo);
it just doesn't matter. This could be rewritten as
foos.forEach(foo -> System.out.println(foo));
or even
foos.forEach(System.out::println);
But really, this code is so simple that it's hard to argue that one way is clearly better.
There are situations where the scales tip in one direction or another. If the loop body can throw a checked exception, a for-loop is clearly better. If the loop body is pluggable (e.g., the Consumer is passed in as a parameter) or if internal iteration has different semantics (e.g., locking of a synchronized list during the entire call to forEach) then the new forEach approach has the edge.
The updated example,
for (String foo : foos) {
String bar = bars.get(foo);
if (bar != null)
System.out.println(foo);
}
is a bit more complicated, but only slightly. I would not write this using a multi-line lambda:
foos.forEach(foo -> {
String bar = bars.get(foo);
if (bar != null)
System.out.println(foo);
});
This offers no advantage over the straight for-loop, in my opinion, and the different semantics of the lambda are signaled by the little arrow way up in the corner of the first line. However, (similar to Bringer128's answer) I would recast this from a big forEach block into a stream pipeline:
foos.stream()
.filter(foo -> bars.get(foo) != null)
.forEach(System.out::println)
I think the lambda/streams approach starts to show a bit of an advantage here, but only a bit, as this is still a really simple example. Using lambda/streams replaces some conditional control logic with a data filtering operation. This might make sense for some operations, but not for others.
The difference between the approaches starts to become clearer as things get more complicated. The simple examples are so simple that it's obvious what they do. Real-world examples can be considerably more complex. Consider this code from the method Class.getEnclosingMethod of the JDK (scroll to lines 1023-1052):
Class<?> enclosingCandidate = enclosingInfo.getEnclosingClass();
// ...
for(Method m: enclosingCandidate.getDeclaredMethods()) {
if (m.getName().equals(enclosingInfo.getName()) ) {
Class<?>[] candidateParamClasses = m.getParameterTypes();
if (candidateParamClasses.length == parameterClasses.length) {
boolean matches = true;
for(int i = 0; i < candidateParamClasses.length; i++) {
if (!candidateParamClasses[i].equals(parameterClasses[i])) {
matches = false;
break;
}
}
if (matches) { // finally, check return type
if (m.getReturnType().equals(returnType) )
return m;
}
}
}
}
throw new InternalError("Enclosing method not found");
(Some security checks and comments have been omitted for the sake of the example.)
Here we have a couple nested for-loops with a couple levels of conditional logic and a boolean flag. Read through this code for a while and see if you can figure out what it does.
Using lambda and streams, this code can be rewritten as follows:
return Arrays.stream(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName()))
.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.findFirst()
.orElseThrow(() -> new InternalError("Enclosing method not found");
What's going on in the classic version is that the loop control and conditional logic is all about searching a data structure for a match. It's a bit contorted because it breaks early out of the inner loop if it detects a non-match, but returns early from the method if it does find a match. But once you stare at this code long enough, you can see that it's searching for the first element that matches a series of criteria, and returns it; and if it doesn't find one, it throws an error. Once you realize that, the lambda/streams approach just pops right out. Not only is it a lot shorter, it's much easier to understand what it's doing.
There are certainly for-loops that will have weird conditions and side effects that can't be turned easily into streams. But there are a lot of for-loops that are just searching data structures, processing elements conditionally, returning the first match, or accumulating a collection of matches, or accumulating transformed elements. These operations naturally lend themselves to being rewritten into streams, and dare I say, in an idiomatic fashion.
In general the lambda form is more idiomatic for single-statement loops, whereas the non-lambda makes more sense for multi-statement loops. (This ignores composing into a more functional style if possible).
One more style you didn't mention is the method reference:
foos.forEach(System.out::println);
EDIT:
As you're looking for a more general answer; you might find that since lambdas are new in Java, the List.forEach method is less used in practice.
In response to "So why is non-lambda more idiomatic for multi-statement?", it's more the reverse, that multi-statement lambdas are not idiomatic in most languages. Lambdas tend to be used for composition, so if I was to take the example from your question and compose it into a functional style:
// Thanks to #skiwi for fixing this code
foos.stream().filter(foo -> bars.get(foo) != null).forEach(System.out::println);
In the above example, using multi-statement lambdas would make it harder to read rather than easier.
You should only be using the new stream/list's forEach if it really makes your code more concise, else stick with the old version, especially for code that gets executed linearly.
I would rewrite your statement to the following, which does make sense with streams:
foos.stream()
.filter(foo -> (bars.get(foo) != null))
.forEach(System.out::println);
This is a functional approach, that will:
Turn your List<String> into a Stream<String>.
Filter the objects such that you retain all elements of which bars.get(foo) is not null, which is of type Predicate<String>.
Then you call System.out::println on the Stream<String>, which resolves to bar -> System.out.println(bar), which is of type Consumer<String>.
So in more normal words:
Obtain a stream.
Filter out all unwanted elements, retain the wanted ones.
Consume all elements from the stream.

Categories

Resources