SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • July 3, 2023
  • Rss Fetcher

Code readability, performance, and limitations of Streams

Image generated with Stable Diffusion

The release of Java 8 was a momentous occasion in Java’s history. Streams and Lambdas were introduced, and they’re now being used widely. If you don’t know about Streams or have never heard of it, it’s completely fine. In most cases, loops will meet your needs, and you’ll have no trouble without Streams.

Then why do we need Streams? Can they replace or have benefits over loops? In this article, we will look into the code, compare performance, and see how well Streams do as a replacement for loops.

Code Comparison

Streams increase code complexity as they need classes, interfaces, and imports; loops are, in contrast, built-in by nature. This is true in some points, but not necessarily. Code complexity is a lot more than how many things you need to know. It’s more about how readable the code is. Let’s look at some examples.

List of item names with a type

Let’s say that we have a list of items and want the list of names of specific item types. Using loops, you will write the following:

List<String> getItemNamesOfType(List<Item> items, Item.Type type) {
List<String> itemNames = new ArrayList<>();
for (Item item : items) {
if (item.type() == type) {
itemNames.add(item.name());
}
}
return itemNames;
}

Reading the code, you’ll see that a new ArrayList should be instantiated, and type check and add() call should be made in every loop. On the other hand, here’s the stream version of the same result:

List<String> getItemNamesOfTypeStream(List<Item> items, Item.Type type) {
return items.stream()
.filter(item -> item.type() == type)
.map(item -> item.name())
.toList();
}

With the help of Lambda, you can immediately catch that we’re first choosing the items with the given type, then getting the list of names of the filtered items. In this kind of code, the line-by-line flow aligns well with the logical flow.

Generate a random list

Let’s look at another example. In the Time Comparison section, we’ll review key Streams methods and compare their execution time with loops. For this, we need a random list of Items. Here is a snippet with a static method that gives a random Item:

public record Item(Type type, String name) {
public enum Type {
WEAPON, ARMOR, HELMET, GLOVES, BOOTS,
}

private static final Random random = new Random();
private static final String[] NAMES = {
"beginner",
"knight",
"king",
"dragon",
};

public static Item random() {
return new Item(
Type.values()[random.nextInt(Type.values().length)],
NAMES[random.nextInt(NAMES.length)]);
}
}

Now, let’s make a list of random Items using loops. The code will look like this:

List<Item> items = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
items.add(Item.random());
}

The code with Streams looks like this:

List<Item> items = Stream.generate(Item::random).limit(length).toList();

A wonderful and easy-to-read code. Furthermore, the List returned by toList() method is unmodifiable, giving you immutability so you can share it anywhere in the code without worrying about side effects. This makes the code less error-prone, and the readers understand your code more easily.

Streams provide a variety of helpful methods that let you write concise codes. The most popular ones are:

  • allMatch()
  • anyMatch()
  • count()
  • filter()
  • findFirst()
  • forEach()
  • map()
  • reduce()
  • sorted()
  • limit()
  • And more in Stream Javadoc

Performance

Streams behave like loops in normal circumstances and have little or no effect on execution time. Let’s compare some major behaviors in Streams with loop implementations.

Iterate elements

When you have a collection of elements, there are a plethora of cases where you iterate all the elements inside the collection. In Streams, methods like forEach(), map(), reduce(), and filter() do this kind of whole-element iteration.

Let’s think of a case where we want to count each type of item inside a list. The code with the for loop will look like this:

public Map<Item.Type, Integer> loop(List<Item> items) {
Map<Item.Type, Integer> map = new HashMap<>();
for (Item item : items) {
map.compute(item.type(), (key, value) -> {
if (value == null) return 1;
return value + 1;
});
}
return map;
}

The code with Streams looks like this:

public Map<Item.Type, Integer> stream(List<Item> items) {
return items.stream().collect(Collectors.toMap(
Item::type,
value -> 1,
Integer::sum));
}

They look quite different, but how will they perform? Below is the table of average execution times of 100 tries:

As we can see in the above comparison table, Streams and loops show little execution time difference in iterating the whole list. This is the same for other Stream methods like map(), forEach(), reduce(), etc., in most cases.

Optimization with parallel stream

So, we found that Streams don’t perform better or worse than loops when iterating the list. However, there is an amazing thing about Streams that loops do not have: we can easily perform multi-thread computing with streams. All you have to do is to use parallelStream() instead of stream().

To see how much impact we can gain from this, let’s look at the following example where we mock the long-taking task as follows:

private void longTask() {
// Mock long task.
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

Looping through the list will look like this:

protected void loop(List<Item> items) {
for (Item item : items) {
longTask();
}
}

Streams will look like this:

protected void stream(List<Item> items) {
items.stream().forEach(item -> longTask());
}

And finally, parallel streams will look like this:

protected void parallel(List<Item> items) {
items.parallelStream().forEach(item -> longTask());
}

Notice that only stream() has changed to parallelStream().

Here is the comparison:

As expected, loops and Streams show little difference. Then what about parallel streams? Sensational! It’s saving more than 80% of the execution time compared to other implementations! How is this possible?

Regarding tasks that take a long time to finish and should be done for each element in the list independently, they can run simultaneously, and we can expect significant improvement. This is what parallel streams are doing. They distribute them into multiple threads and make them run simultaneously.

Parallel streams are only sometimes a winner that you can use everywhere instead of loops or Streams. It is only useful when the tasks are independent. If the tasks are not independent and have to share the same resources, you’ll have to keep them safe with a lock, mainly by synchronized keyword in Java, and make them run slower than normal iterations.

Limitations

Streams, however, also have limitations. One case is conditional loops, and another one is repetitions. Let’s see what they mean.

Conditional loops

When we want to repeat until the condition is true but are not sure how many iterations it will take, we normally use the while loop.

boolean condition = true;
while (condition) {
...
condition = doSomething();
}

The code that behaves the same using Streams looks like this:

Stream.iterate(true, condition -> condition, condition -> doSomething())
.forEach(unused -> ...);

You can see that some boilerplate parts bother the reading, such as condition -> condition that checks whether the condition is true, and unused parameter inside the forEach(). Considering this, conditional loops are better written in while loops.

Repetition

Repetition is one of the main reasons for the for loop’s existence. Let’s say we want to repeat the process ten times. With the for loop, it can be easily written as:

for (int i = 0; i < 10; i++) {
...
}

In Streams, one way to achieve this is to make an IntStream that contains [0, 1, 2, … , 9] and iterate it.

IntStream.range(0, 10).forEach(i -> ...);

Although the code may look concise and proper, it looks more focused on the values of the range 0 to 10 (exclusive), where the for loop code can be read repeat ten times as it’s more general to write repeat in this way: starting from 0 and ending having the number of repetition times.

Summary

We’ve gone through some comparisons between Streams and loops. So… can Streams replace loops? Well, as always, it depends on the situation! However, Streams can usually provide you with more concise, easy-to-read code and optimizations.

So, what are you waiting for? Go ahead and start writing your codes with Streams!

The codes written for this article can be found on my GitHub.


Can Streams Replace Loops in Java? was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Salesforce acquires Informatica for $8 billion
  • Tesla loses more ground in Europe, while BYD makes gains
  • YouTube tops Disney and Netflix in TV viewing, Nielsen finds
  • WhatsApp launches long-awaited iPad app
  • Ahead of WWDC, Apple says App Store blocked $2B in fraud transactions last year, $9B in past 5 years

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.