I mean the question in the sense of: Should the occurrence of simple loops on collections in code in Java 8 and higher be regarded as code smell (except in justified exceptions)?
When it came to Java 8, I assumed that it would be good to treat everything with Stream
API now wherever possible. I thought, especially when I use parallelStream()
wherever I know that order doesn't matter, this gives the JVM the ability to optimize the execution of my code.
Teammates think differently here. They think lambda transforms are hard to read and we don't use streams much now. I agree that streams are hard to read if the code formatter forces you to write them like this:
return projects.parallelStream().filter(lambda -> lambda.getGeneratorSource() != null) .flatMap(lambda -> lambda.getFolders().prallelStream().map(mu -> Pair.of(mu, lambda.getGeneratorSource()))) .filter(lambda -> !lambda.getLeft().equals(lambda.getRight())).map(Pair::getLeft) .filter(lambda -> lambda.getDerivative().isPresent() || lambda.getDpi().isPresent() || lambda.getImageScale().isPresent() || lambda.getImageSize().isPresent()) .findAny().isPresent();
I would rather prefer to write them like this:
return projects.parallelStream() // skip all projects that don’t declare a generator source .filter(λ -> λ.getGeneratorSource() != null ) /* We need to remove the folders which are the source folders of their * project. To do so, we create pairs of each folder with the source * folder … */ .flatMap(λ -> λ.getFolders().stream() .map(μ -> Pair.of(μ, λ.getGeneratorSource()) // Pair<Folder, Folder> ) ) // … and drop all folders that ARE the source folders .filter(λ -> !λ.getLeft().equals(λ.getRight()) ) /* For the further processing, we only need the folders, so we can unbox * them now */ .map(Pair::getLeft) // only look for folders that declare a method to generate images .filter(λ -> λ.getDerivative().isPresent() || λ.getDpi().isPresent() || λ.getImageScale().isPresent() || λ.getImageSize().isPresent() ) // return whether there is any .findAny().isPresent();
Yes, return
is at the top, and it looks quite differently than:
for (Project project : projects) { if (Objects.nonNull(project.getGeneratorSource())) { continue; } for (Folder folder : project.getFolders()) { if (folder.equals(project.getGeneratorSource())) { continue; } else if (folder.getDerivative().isPresent() || folder.getDpi().isPresent() || folder.getImageScale().isPresent() || folder.getImageSize().isPresent()) { return true; } } } return false;
But isn’t that just a matter of habit? Therefore my question is more a question of assessment:
Should Stream
be something basic to use (should Java beginners learn for
loops at all/first, or should they first learn to use streams), or is that too much premature optimization, and one should use streams rather after it is shown that a code makes a bottleneck.
(Less opinion-based: How do modern Java teaching books (are there still books in IT training?) handle this?)
Edit 1: To prevent the question from losing focus: My question is: Is Stream
is intended as a basic programming paradigm every Java programmer should use often, or as a performance feature for senior software enhancers, that should be avoided? You can also look at it internally: Can the JVM decide whether to use a stream construction or process the objects sequentially (e.g. may it only do that if it detects a hotspot?), or does it have to set up complicated parallelization logic in the background each time, with multiple threads, so that this could produce a lot of overhead? In the second case that would be a clear argument to me against using Stream
all and everywhere.