Using Streams to replace loops - java

I have the following code:
boolean found = false;
for (Commit commit : commits.values()) {
if (commit.getLogMessage().compareTo(commitMessageToSearch) == 0) {
System.out.println(commit.getSha());
found = true;
}
}
if (!found) {
System.out.println("aksdhlkasj");
}
Is there some way to write this succinctly using streams or anything else in Java

You can use Stream#filter along with Stream#findFirst.
System.out.println(commits.values().stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.findFirst().map(Commit::getSha).orElse("aksdhlkasj"));

In case you want to print out all the occurrences and print some String only in case, there was no item found, I am afraid there is no way other than collecting all the relevant sha values into a list and checking for its emptiness using Optional:
commits.values()
.stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.peek(System.out::println)
.collect(Collectors.collectingAndThen(Collectors.toList(), Optional::of))
.filter(List::isEmpty)
.ifPresent(emptyList -> System.out.println("aksdhlkasj"));
Although the intermediate output Optional<List<?>> is against common sense, it helps the further processing using Optinal and comfortably handling the case the list is empty.
However, this form is in my opinion more readable:
List<String> shaList = commits.values()
.stream()
.filter(commit -> commit.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.peek(System.out::println)
.collect(Collectors.toList());
if (shaList.isEmpty()) {
System.out.println("aksdhlkasj");
}

The following options are available:
Use StreamEx library (maven repo) and its quasi-intermediate operation StreamEx::ifEmpty:
import one.util.streamex.StreamEx;
// ...
String msgSearch = "commit message";
StreamEx.of(commits.values())
.filter(c -> c.getLogMessage().compareTo(msgSearch) == 0)
.ifEmpty("no commit message found: " + msgSearch)
.map(Commit::getSha)
.forEach(System.out::println);
Plus: very concise and clean, single run
Minus: use of a 3rd-party lib
Check the contents of the stream using short-circuiting match without redundant collecting into a list just to check if it's empty
if (commits.values().stream()
.map(Commit::getLogMessage) // equals should be equivalent to compareTo == 0
.anyMatch(msgSearch::equals)
) {
commits.values().stream()
.filter(c -> c.getLogMessage().compareTo(msgSearch) == 0)
.map(Commit::getSha)
.forEach(System.out::println);
} else {
System.out.println("no commit message found: " + msgSearch);
}
Plus: using standard API (no 3-rd party) without side-effect
Minuses: too verbose, in the worst case (long list with the matching element in the end) double iteration of the stream
Use effectively final variable and its setting from the stream as a side effect.
Disclaimer: usage of stateful streams and side effects are not recommended in general:
The best approach is to avoid stateful behavioral parameters to stream operations entirely...
Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards.
A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care
Thus, if AtomicBoolean is carefully selected as a thread-safe and fast container of a boolean value for found flag, which is only set to true from inside the stream (never reset), the following solution may be offered and the risks of safety and performance are mitigated:
// effectively final and thread-safe container boolean
AtomicBoolean found = new AtomicBoolean();
commits.values().stream()
.filter(c -> c.getLogMessage().compareTo(commitMessageToSearch) == 0)
.map(Commit::getSha)
.forEach(sha -> {
found.set(true);
System.out.println(sha);
});
if (!found.get()) {
System.out.println("none found");
}
Plus: using standard API (no 3rd-party library), single run, no redundant collection
Minus: using side-effect, discouraged by purists, just mimics of for-each loop

Related

Iterate with streams (with a flag in for) [keep state within lambda]

I have two lists, allowedOU and parts. I need to know how to iterate through streams, check the condition and if it is true, include the element in a third list, and change the flag (heritable).
for (String part : parts) {
for (BeanOU it : allowedOU) {
if (part.startsWith("OU") && it.OU.equals(part.substring(3) && heritableFlag) {
list.add(part.substring(3, part.length()));
heritableFlag = it.heritable;
break;
}
}
}
I tried something like this
parts.stream()
.filter(parte -> allowedOU.stream()
.anyMatch(allowed -> (parte.startsWith("OU"))
&& allowed.OU.equals(parte.substring(3, parte.length()))
&& finalHeritableFlag))
.forEach(here we don't have it variable...)
"The results of the flow pipeline can be non-deterministic or incorrect if the behavior parameters of the flow operations are stateful."
"Most stream operations accept parameters that describe user-specified behavior, which are often lambda expressions. To preserve correct behavior, these behavior parameters should not interfere, and in most cases should be stateless. Such parameters are always instances of a functional interface such as Function, and are often lambda expressions or method references".
I leave this in case someone needs it!
an approximation to stop when a certain condition is met, it offers us is takeWhile
List<String> partsList = Arrays.asList(parts);
partsList = partsList.stream().filter((a->a.startsWith("OU"))).map(s->s.substring(3)).collect(Collectors.toList());
List<String> finalPartsList1 = partsList;
listOUallowedByUser = allowedOU.stream().takeWhile(allowed -> allowed.heritable).filter(f-> finalPartsList1.contains(f.OU)).map(a-> a.OU).collect(Collectors.toList());;
thanks for mark-rotteveel and alexander-ivanchenko :-)

How could I simplify this with Java stream library?

for (Issue issue : issues) {
if (issue.getSubtasks().spliterator().estimateSize() > 0) {
alreadyCreated = false;
for (Subtask subtask : issue.getSubtasks()) {
if (subtask.getSummary().contains("sometext")) {
alreadyCreated = true;
break;
}
}
if (!alreadyCreated) {
ids.add(issue.getKey());
}
} else {
ids.add(issue.getKey());
}
}
I'm not so expert with Java stream API, but I'm pretty sure there's some way to simplify the code above with using lambda expressions. Would be a great help to understand it even better!
Some notes:
Issues and getSubtasks() are returning back Iterable<> types.
You can use filter to remove unwanted elements and map to get the key, then collect to a List.
StreamSupport.stream(issues.spliterator(), false).filter(x ->
StreamSupport.stream(x.getSubtasks().spliterator(), false)
.noneMatch(y -> y.getSummary().contains("sometext"))
).map(Issue::getKey).collect(Collectors.toList());
I can't be certain what is streamable in your example so I'm going to provide an alternate solution that doesn't require streams but is at least, if not more, efficient. It just uses a different technique but essentially the same logic.
if size <= 0, then the for loop is skipped and the key is added.
if size > 0 then then for loop is excecuted. Then if any of the summaries contains the text, the outer loop proceeds normally, otherwise, the loop falls thru ant the key is added.
outer:
for (Issue issue : issues) {
if (issue.getSubtasks().spliterator().estimateSize() > 0) {
for (Subtask subtask : issue.getSubtasks()) {
if (subtask.getSummary().contains("sometext")) {
continue outer;
}
}
}
ids.add(issue.getKey());
}
}
You're adding the issue key to ids if there exists no subtask with a summary containing "sometext."
issues.stream()
.filter(i -> i.getSubtasks().stream()
.noneMatch(s -> s.getSummary().contains("sometext")))
.map(Issue::getKey)
.forEach(ids::add);
I think the subtasks size check is at least superfluous and possibly dangerous, if it's possible for it to estimate the size as zero when subtasks exist. But if getSubtasks() returns some non-collection that can't easily be streamed and this estimateSize() call is necessary then that just changes things a little bit:
issues.stream()
.filter(i -> {
Spliterator<Subtask> split = i.getSubtasks().spliterator();
return split.estimateSize() == 0 ||
StreamSupport.stream(split, false)
.noneMatch(s -> s.getSummary().contains("sometext"));
})
.map(Issue::getKey)
.forEach(ids::add);

Nested parallel stream execution in Java - findAny() randomly fails

The following code throws the IllegalArgumentException in every 10-15 try for the same input:
AllDirectedPaths<Vertex, Edge> allDirectedPaths = new AllDirectedPaths<>(graph);
List<GraphPath<Vertex, Edge>> paths = allDirectedPaths.getAllPaths(entry, exit, true, null);
return paths.parallelStream().map(path -> path.getEdgeList().parallelStream()
.map(edge -> {
Vertex source = edge.getSource();
Vertex target = edge.getTarget();
if (source.containsInstruction(method, instructionIndex)) {
return source;
} else if (target.containsInstruction(method, instructionIndex)) {
return target;
} else {
return null;
}
}).filter(Objects::nonNull)).findAny().flatMap(Stream::findAny)
.orElseThrow(() -> new IllegalArgumentException("Given trace refers to no vertex in graph!"));
The idea of the code is to find a vertex that wraps a certain instruction (see containsInstruction()), whereas the vertex is on at least one path from the entry to the exit vertex. I'm aware that the code is not optimal in terms of performance (every intermediate vertex on a path is looked up twice), but that doesn't matter.
The input is simply a trace (String) from which the method and instructionIndex can be derived. All other variables are fixed in that sense. Moreover, the method containsInstruction() doesn't have any side effects.
Does it matter where to put the 'findAny()' stream operation? Should I place it directly following the filter operation? Or are nested parallel streams the problem?
You should use .flatMap(path -> ... ) and remove .flatMap(Stream::findAny).
Your code doesn't work because the first findAny() returns a stream that is always non null, but that might hold null elements.
Then, when you apply the second findAny() by means of the Optional.flatMap(Stream::findAny) call, this last find operation might return an empty Optional, as the result of ending up with a null element of the inner stream.
This is how the code should look:
return paths.stream()
.flatMap(path -> path.getEdgeList().stream()
.map(edge ->
edge.getSource().containsInstruction(method, instructionIndex) ?
edge.getSource() :
edge.getTarget().containsInstruction(method, instructionIndex) ?
edge.getTarget() :
null)
.filter(Objects::nonNull))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("whatever"));
Note aside: why parallel streams? There doesn't seem to be CPU bound tasks in your pipeline. Besides, parallel streams create a lot of overhead. They are useful in very few scenarios, i.e. tens of thousands of elements and intensive CPU operations along the pipeline
EDIT: As suggested in the comments, the map and filter operations of the inner stream could be safely moved to the outer stream. This way, readability is improved and there's no difference performance-wise:
return paths.stream()
.flatMap(path -> path.getEdgeList().stream())
.map(edge ->
edge.getSource().containsInstruction(method, instructionIndex) ?
edge.getSource() :
edge.getTarget().containsInstruction(method, instructionIndex) ?
edge.getTarget() :
null)
.filter(Objects::nonNull)
.findAny()
.orElseThrow(() -> new IllegalArgumentException("whatever"));
Another note: maybe refactoring the code inside map to a method of the Edge class would be better, so that the logic to return either the source, the target or null is in the class that already has all the information.

Check all objects inside collection should have length greater than 0

I have a code snippet like this
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getModelNumber())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getMaterial())) {
productLinks.add(DETAILS);
break;
} else if (StringUtils.isNotEmpty(data.getColour())) {
productLinks.add(DETAILS);
break;
}
}
}
As you can see, I am iterating a collection and doing a check in order to populate the link "details" in front end. The idea is that I need to populate this link at least one of the attribute length inside current object should be > 0. Because of the fact, I have used so many break statement, this snippet is failing in sonar build process
What do I need? I request you guys to share me the simplest version of the above code or refactored code using latest JDK and yes we are using JDK 11 and I am not pretty sure about the methods that I need to use for this kind of check.
If there is no other alternatives how to overcome this "Loops should not contain more than a single "break" or "continue" statement" sonar issue.
Appreciate your time and effort on this.
The easiest solution could be just to join multiple if statements into one though it may not be helpful against Sonar rules :)
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
for (final SpecificationData data : target.getSpecifications()) {
if (StringUtils.isNotEmpty(data.getModelName())
|| StringUtils.isNotEmpty(data.getModelNumber())
|| StringUtils.isNotEmpty(data.getMaterial())
|| StringUtils.isNotEmpty(data.getColour())
) {
productLinks.add(DETAILS);
break;
}
}
}
However, you may use stream operations such as filter and findFirst like this without any for loop and break statements:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.filter(x ->
Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour())
.filter(StringUtils::isNotEmpty)
.findFirst()
.isPresent()
)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}
UPDATE
For this specific case it is also possible to use flatMap to detect any first non-empty property and perform an action:
if (CollectionUtils.isNotEmpty(target.getSpecifications())) {
target.getSpecifications().stream()
.flatMap(x -> Stream.of(x.getModelName(), x.getModelNumber(), x.getMaterial(), x.getColour()))
.filter(StringUtils::isNotEmpty)
.findFirst()
.ifPresent(x -> productLinks.add(DETAILS));
}

Java Lambda Expression for if condition - not expected here

Consider the case where an if condition needs to evaluate an array or a List. A simple example: check if all elements are true. But I'm looking for generic way to do it
Normally I'd do it like that:
boolean allTrue = true;
for (Boolean bool : bools){
if (!bool) {
allTrue = false;
break;
}
}
if (allTrue){
// do Something
}
But now I'd like to hide it into my if condition. I tried using Lambda Expressions for this, but it's not working:
if (() -> {
for (Boolean bool : bools)
if (!bool)
return false;
return true;
}){
// do something
}
If this were working I could do something more complicated like
if (() -> {
int number = 0;
for (MyObject myobject : myobjects)
if (myObject.getNumber() != 0)
numbers++;
if (numbers > 2)
return false;
return true;
}{
//do something
}
Is there a better way to do it is it just a syntax error?
UPDATE
I'm not talking about the boolean array, rather looking for a generic way to achieve that.
You can write, given for instance a List<Boolean>:
if (!list.stream().allMatch(x -> x)) {
// not every member is true
}
Or:
if (list.stream().anyMatch(x -> !x)) {
// at least one member is false
}
If you have an array of booleans, then use Arrays.stream() to obtain a stream out of it instead.
More generally, for a Stream providing elements of (generic) type X, you have to provide a Predicate<? super X> to .{all,any}Match() (either a "full" predicate, or a lambda, or a method reference -- many things go). The return value of these methods are self explanatory -- I think.
Now, to count elements which obey a certain predicate, you have .count(), which you can combine with .filter() -- which also takes (whatever is) a Predicate as an argument. For instance checking if you have more than 2 elements in a List<String> whose length is greater than 5 you'd do:
if (list.stream().filter(s -> s.length() > 5).count() > 2L) {
// Yup...
}
Your problem
Your current problem is that you use directly a lambda expression. Lambdas are instances of functional interfaces. Your lambda does not have the boolean type, that's why your if does not accept it.
This special case's solution
You can use a stream from your collections of booleans here.
if (bools.stream().allMatch((Boolean b)->b)) {
// do something
}
It is actually much more powerful than this, but this does the trick I believe.
General hint
Basically, since you want an if condition, you want a boolean result.
Since your result depends on a collection, you can use Java 8 streams on collections.
Java 8 streams allow you to do many operations on a collection, and finish with a terminal operation. You can do whatever complicated stuff you want with Stream's non-terminal operations. In the end you need one of 2 things:
use a terminal operation that returns a boolean (such as allMatch, anyMatch...), and you're done
use any terminal operation, but use it in a boolean expression, such as myStream.filter(...).limit(...).count() > 2
You should have a look at your possibilities in this Stream documentation or this one.

Categories