Java - Rewriting Traditional Codes With Streams #1 (filter, foreach)

Ömer Kurular

--

In this series, we are going to see examples on traditional Java codes and their Stream versions. We will start off by fundamentals in this first part and later, it will be getting a bit more advanced on every part.

Iterating over elements of a list: forEach

private static void printListElements(List<Integer> elements) {
for (Integer element : elements) {
System.out.println(element);
}
}

private static void printListElementsUsingStreams(List<Integer> elements) {
elements.stream().forEach(element -> System.out.println(element));
}

private static void printListElementsUsingStreamsAndMethodReference(List<Integer> elements) {
elements.stream().forEach(System.out::println);
}

The first method is the traditional one. A simple foreach loop prints the elements. The second one uses forEach method of Streams api to print the elements of the list. Here, we use lambda expressions to define behaviour for what we will do with each element of the list. The third one still uses stream and also uses method reference to reduce verbose.

There is also forEach method in the collections api but as we are talking about streams, we used forEach of stream api. But keep this in mind that when you just need to iterate over a collection, you better stick to forEach of the collections api like list.forEach(System.out::println), as forEach of streams is mostly for iteration over elements after a set of stream operations such as filtering, mapping etc.

We have a variant of forEach method called forEachOrdered. In parallel streams, elements may be processed in a different order than the order stream is fed. So, if we want to keep the order, we can use forEachOrdered.

Filtering elements: filter

private static void filterListElements(List<Integer> elements) {
for (Integer element : elements) {
if (element % 2 == 0) {
System.out.println(element);
}
}
}

private static void filterListElementUsingStreams(List<Integer> elements) {
elements.stream().filter(element -> element % 2 == 0).forEach(System.out::println);
}

When we need to filter our list to have only required kind of elements such as even integers, we use if block inside for loop in traditional way. What if we want to enhance our code using streams. As you can see above, stream api provides filter method which accepts Predicate interface to eleminate elements.

@FunctionalInterface
public interface Predicate<T> {

/**
* Evaluates this predicate on the given argument.
*
*
@param t the input argument
*
@return {@code true} if the input argument matches the predicate,
* otherwise {
@code false}
*/
boolean test(T t);
// rest omitted for brevity
}

Our lambda expression here takes an elements of the list and runs the given expression over it.

element -> element % 2 == 0 // --> implementation of the test method in the Predicate interface

Our predicate tests if the current element is even.

We can also use multiple filter interface chained one after another.

private static void filterListElementUsingStreamsWithMultipleFilter(List<Integer> elements) {
elements.stream()
.filter(element -> element % 2 == 0)
.filter(element -> element > 5)
.forEach(System.out::println);
}

There is an importat thing about stream operations. Stream operations does not mutate the stream source they are performing operations over. For example, the filter operation we saw above does not remove unqualifying elements off the list but instead, creates an intermediate state and operates over it.

Now, we can take a look at some questions on iterations and filter.

1- Print the numbers that are divisible by 7 and either lower than -100 or greater than 100.

We will have such numbers from the list that none will be between -100 and 100 and all will be divisible by 7.

Traditional way

private static void printNumbersDivisibleBy7AndEitherLowerThanMinus100orGreaterThan100(List<Integer> numbers) {
for (Integer number : numbers) {
if (number % 7 == 0 && (number < -100 || number > 100)) {
System.out.println(number);
}
}
}

Stream way

private static void printNumbersDivisibleBy7AndEitherLowerThanMinus100orGreaterThan100(List<Integer> numbers) {
numbers.stream()
.filter(number -> number % 7 == 0)
.filter(number -> number > 100 || number < -100)
.forEach(System.out::println);
}

2- Print the strings that given set contains and starts with given letter.

Firstly, we can eleminate the strings not starting with given prefix. Than we can check that the set contains it.

Traditional way

private static void printStringsThatGivenSetContainsAndStartsWithGivenPrefix(List<String> string, Set<String> set, String prefix) {
for (String s : string) {
if (s.startsWith(prefix) && set.contains(s)) {
System.out.println(s);
}
}
}

Stream way

private static void printStringsThatGivenSetContainsAndStartsWithGivenPrefix(List<String> string, Set<String> set, String prefix) {
string.stream()
.filter(s -> s.startsWith(prefix))
.filter(set::contains)
.forEach(System.out::println);
}

I also need to state that you should not always use streams instead of traditional way. If you really believe that it enhances your code like it makes it more readible, you should prefer streams. So, do not use streams just because it looks cooler. For the examples above, I would go for traditional way but when we see other stream operations, they will make a good use together and they will come to a point that makes sense to replace traditional way.

In the first part of the stream series, we talked about fundamental operations for iterating and filtering collections. Next, we will be working on other operations and their traditional versions ,and as we are having more functions to work with, we will be solving complex questions using streams to have better understanding.

--

--

No responses yet