Using ‘if-else’ Conditions with Java Streams
Learn to use the if-else conditions logic using Java Stream API to filter the items from a collection based on certain conditions.
1. The ‘ if-else ‘ Condition as Consumer Implementation
The ‘if-else’ condition can be applied as a lambda expression in forEach() function in form of a Consumer action.
Consumer is a functional interface whose functional method is ‘ void accept(Object) ‘. It represents an operation that accepts a single input argument and returns no result.
In the given example, we are checking if a number is even then print a message, else print another message for an odd number.
ArrayList numberList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6)); Consumer action = i -> < if (i % 2 == 0) < System.out.println("Even number :: " + i); //Or any other user action we want to do >else < System.out.println("Odd number :: " + i); //Or any other user action we want to do >>; numberList.stream() .forEach(action);
- We can perform any kind of operation on the stream items instead of just printing the items to the console, e.g. storing the items to two separate lists or passing the items to other method calls.
- We can write as many if-else statements as required.
- We can also write the pass the Consumer implementation as an inline lambda expression to the forEach() function.
Arrays.asList(-1, 1, -2, 3, 4, -5, 6, 0).stream() .forEach( i -> < if (i == 0) < System.out.println("Number is 0"); >else if (i > 0) < System.out.println("Positive Number"); >else < System.out.println("Negative Number"); >> );
2. The ‘ if’ Condition with Predicates
If we intend to apply only ‘if’ logic then we can pass the condition directly do the filter() function as a Predicate.
In the given example, we are checking if a number is an even number then printing a message.
ArrayList numberList = new ArrayList<>(Arrays.asList(1,2,3,4,5,6)); Predicate isEven = i -> i % 2 == 0; numberList.stream() .filter(isEven) .forEach(System.out::println);
Using one of the above given two methods, we can apply any combination of if-else conditions in Java 8 stream elements.
use if-else statement in java-8 lambda expression
Now I want to convert my code to Stream & collect in java-8, I implement half of my business about filter not null Ncourse :
equivalentCourses.stream().filter(obj -> obj.getNcourse() != null ) .map(obj -> obj.getNcourse().getId()).collect(Collectors.joining(","));
Why do you use Character instead of char ? This makes your code harder to read and wastes resources. Though, you don’t need it at all, if you replace !ncourseIds.isEmpty() && ha.equals(ncourseIds.charAt(ncourseIds.length()-1)) with a simple ncourseIds.endsWith(«,») and likewise, !pastCourseIds.isEmpty() && cha.equals(pastCourseIds.charAt(pastCourseIds.length()-1)) with pastCourseIds.endsWith(«,») . To collect two strings with streams, you can simple perform two stream operations.
@Holger already said it: use two stream operations (one for getNcourse() != null and one for getNcourse() == null && getPastCourse() != null ).
3 Answers 3
As a stream call chain is complex make two streams — avoiding the conditional branches.
String ncourseIds = equivalentCourses.stream() .filter(equivalentCourse -> equivalentCourse.getNcourse() != null) .map(EquivalentCourse::getNcourse) .map(x -> String.valueOf(x.getId())) .collect(Collectors.joining(", ")); String pastCourseIds = equivalentCourses.stream() .filter(equivalentCourse -> equivalentCourse.getNcourse() == null && equivalentCourse.getPastCourse() != null) .map(EquivalentCourse::getPastCourse) .map(x -> String.valueOf(x.getId())) .collect(Collectors.joining(", "));
This also is code focusing on the resulting two strings, with an efficient joining.
By the way, if this is for an SQL string, you may use a PreparedStatement with an Array.
Embellishment as commented by @Holger:
String ncourseIds = equivalentCourses.stream() .map(EquivalentCourse::getNcourse) .filter(Objects::nonNull) .map(NCourse::getId) .map(String::valueOf) .collect(Collectors.joining(", ")); String pastCourseIds = equivalentCourses.stream() .filter(equivalentCourse -> equivalentCourse.getNcourse() == null) .map(EquivalentCourse::getPastCourse) .filter(Objects::nonNull) .map(EquivalentCourse::getPastCourse) .map(PastCourse::getId) .map(String::valueOf) .collect(Collectors.joining(", "));
You can simplify by changing the order. First stream op: .map(EquivalentCourse::getNcourse) .filter(Objects::nonNull) , second stream op: .filter(equivalentCourse -> equivalentCourse.getNcourse() == null) .map(EquivalentCourse::getPastCourse) .filter(Objects::nonNull) .
@JoopEggen If equivalentCourses has huge number of elements, this is inefficient. Isn’t? If so, is there any way to make it efficiently?
@Gibbs no, neither in O-complexity, neither in speed or space usage. In an old for-loop one could hold two StringBuilders and append to them in one loop and an internal if-else for appending to either StringBuilder. Making that two for-loops for each StringBuilder would be somewhat the same.
You could group by condition and then remap:
public void booleanGrouping() throws Exception < Liststrings = new ArrayList<>(); strings.add("ala"); strings.add("ela"); strings.add("jan"); strings.stream() .collect( Collectors.groupingBy(s -> s.endsWith("a")) // using function Obj -> Bool not predicate ).entrySet() .stream() .collect( Collectors.toMap( e -> e.getKey() ? "Present" : "Past", e -> e.getValue().stream().collect(Collectors.joining("")) ) ); >
First stream group by condition, you should use equivalentCourse.getNcourse() != null second remap collections from value to string. You could introduce:
and change e -> e.getKey() ? «Present» : «Past» to enum based solution.
public Map booleanGrouping() throws Exception < Liststrings = new ArrayList<>(); strings.add("ala"); strings.add("ela"); strings.add("jan"); // our ifs: /* if(!string.endsWith("n"))< >else if(string.startsWith("e"))<> final map should contains two elements endsWithN -> ["jan"] startsWithE -> ["ela"] NOT_MATCH -> ["ala"] */ return strings.stream() .collect( Collectors.groupingBy(Classifier::apply) // using function Obj -> Bool not predicate ).entrySet() .stream() .collect( Collectors.toMap( e -> e.getKey(), e -> e.getValue().stream().collect(Collectors.joining("")) ) ); > enum Classifier implements Predicate < ENDS_WITH_N < @Override public boolean test(String s) < return s.endsWith("n"); >>, STARTS_WITH_E < @Override public boolean test(String s) < return s.startsWith("e"); >>, NOT_MATCH < @Override public boolean test(String s) < return false; >>; public static Classifier apply(String s) < return Arrays.stream(Classifier.values()) .filter(c ->c.test(s)) .findFirst().orElse(NOT_MATCH); > >
That is good solution only for if-else statement, but question is about else-if statement. In that case you need to perform two stream operation.
I see 🙂 I will fix that (you need introduce a little bit different condition) when I back to my main PC 😀
To add an alternative, here’s what the code would look like to do the work with two filter() operations. Note, this has the impact of iterating over the entire collection a second time, which may have a performance impact if this is a large collection.
I also went ahead and simplified some of the logic in regards to the string joining. Correct me if I missed anything there.
final List courseIdList = new ArrayList<>(); final List pastCourseIdList = new ArrayList<>(); equivalentCourses.stream().filter((current) -> current.getNcourse() != null) .forEach((current) -> courseIdList.add(current.getNcourse().getId())); equivalentCourses.stream().filter((current) -> current.getNcourse() != null && current.getPastCourse() != null) .forEach((current) -> pastCourseIdList.add(current.getPastCourse().getId())); String ncourseIds = String.join(",", courseIdList); String pastCourseIds = String.join(",", pastCourseIdList);
Original answer
For your use case, it may make the most sense to use the forEach() lambda. This will be the easiest way to do the translation.
java.lang.Character cha = new java.lang.Character(','); final StringBuilder ncourseIdBuilder = new StringBuilder(); final StringBuilder pastCourseIdBuilder = new StringBuilder(); equivalentCourses.stream().forEach((equivalentCourse) -> < if (equivalentCourse.getNcourse() != null) < ncourseIdBuilder.append(equivalentCourse.getNcourse().getId()).append(","); >else if (equivalentCourse.getPastCourse() != null) < pastCourseIdBuilder.append(equivalentCourse.getPastCourse().getId()).append(","); >>); String ncourseIds = ncourseIdBuilder.toString(); String pastCourseIds = pastCourseIdBuilder.toString(); if (!ncourseIds.isEmpty() && cha.equals(ncourseIds.charAt(ncourseIds.length() - 1))) < ncourseIds = ncourseIds.substring(0, ncourseIds.length() - 1); >if (!pastCourseIds.isEmpty() && cha.equals(pastCourseIds.charAt(pastCourseIds.length() - 1)))
You can rewrite the code using filter() expressions, but it’ll require a bigger re-working of the logic in the conditionals, which introduces the risk you might break something if this isn’t tested well. The logic changes are exactly what @Holger and @Ole V.V. reference in their comments to the original question.
Whether you use forEach() or the filters, lambdas cannot access non-final variables within the expression, hence why I added the final StringBuilder variable outside the scope of the loop.
Use Java lambda instead of ‘if else’
I can write a similar ifNotExist , and I want they are mutually exclusive (if the exist condition is true, there is no need to check ifNotExist , because sometimes, the exist() method takes so much workload to check), but I always have to check two times. How can I avoid that? Maybe the «exist» word make someone misunderstand my idea. You can imagine that I also need some methods:
ifVisible() ifEmpty() ifHasAttribute()
Many people said that this is bad idea, but: In Java 8 we can use lambda forEach instead of a traditional for loop. In programming for and if are two basic flow controls. If we can use lambda for a for loop, why is using lambda for if bad idea?
for (Element element : list) < element.doSomething(); >list.forEach(Element::doSomething);
Optional element = . element.ifPresent(el -> System.out.println("Present " + el);
And about code maintenance and readability, what do you think if I have the following code with many repeating simple if clauses?
if (e0.exist()) < e0.actionA(); >else < e0.actionB(); >if (e1.exist()) < e0.actionC(); >if (e2.exist()) < e2.actionD(); >if (e3.exist())
e0.ifExist(Element::actionA).ifNotExist(Element::actionB); e1.ifExist(Element::actionC); e2.ifExist(Element::actionD); e3.ifExist(Element::actionB);
Which is better? And, oops, do you notice that in the traditional if clause code, there’s a mistake in: