In this post we’ll look at some alternative solutions to the traditional
loop. The great thing about the new functional features in Java 8, is
that it allows us to say what we want to be done instead of saying how to do it. This is where loops fall short. Sure loops are flexible, but this flexibility doesn’t come without a price. A
Let the coding begin!
This time we’re going to work with articles. An article has a title, an author and several tags.
Each of the examples will contain a traditional loop solution, and a solution using the new features in Java 8.
In the first example we want to find the first article in the collection that has the tag “Java”.
Let’s look at a solution using a for-loop.
Now let’s solve the problem using operations from the stream API.
Pretty cool right? We first use the
Now, lets get all the elements that match instead of just the first.
First, the solution using a for-loop.
Solution using stream operations.
return
, break
or continue
dramatically changes how the loop will act, forcing us not only to
understand what the code is trying to achieve, but also understand how
the loop works.Let the coding begin!
This time we’re going to work with articles. An article has a title, an author and several tags.
private class Article {
private final String title;
private final String author;
private final List<String> tags;
private Article(String title, String author, List<String> tags) {
this.title = title;
this.author = author;
this.tags = tags;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public List<String> getTags() {
return tags;
}
}
Each of the examples will contain a traditional loop solution, and a solution using the new features in Java 8.
In the first example we want to find the first article in the collection that has the tag “Java”.
Let’s look at a solution using a for-loop.
public Article getFirstJavaArticle() {
for (Article article : articles) {
if (article.getTags().contains("Java")) {
return article;
}
}
return null;
}
Now let’s solve the problem using operations from the stream API.
public Optional<Article> getFirstJavaArticle() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.findFirst();
}
Pretty cool right? We first use the
filter
operation to find all articles that have the Java tag, then used the findFirst()
operation to get the first occurrence. Since streams are lazy and
filter returns a stream, this approach only processes elements until it
finds the first match.Now, lets get all the elements that match instead of just the first.
First, the solution using a for-loop.
public List<Article> getAllJavaArticles() {
List<Article> result = new ArrayList<>();
for (Article article : articles) {
if (article.getTags().contains("Java")) {
result.add(article);
}
}
return result;
}
Solution using stream operations.
public List<Article> getAllJavaArticles() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.collect(Collectors.toList());
}
In this example we used the collect operation to perform a reduction on the result stream instead of declaring a collection ourselves and explicitly add the articles if they match.
So far so good. Time to create a few examples that really make the stream API shine! Let's group all the articles based on the author. As usual, we start with the solution using a loop.
public Map<String, List<Article>> groupByAuthor() {
Map<String, List<Article>> result = new HashMap<>();
for (Article article : articles) {
if (result.containsKey(article.getAuthor())) {
result.get(article.getAuthor()).add(article);
} else {
ArrayList<Article> articles = new ArrayList<>();
articles.add(article);
result.put(article.getAuthor(), articles);
}
}
return result;
}
Can we find a clean solution for this problem using the stream operation?
public Map<String, List<Article>> groupByAuthor() {
return articles.stream()
.collect(Collectors.groupingBy(Article::getAuthor));
}
Great! Using the groupingBy operation and a method reference to getAuthor we got some clean and readable code.
Now, let's find all the different tags used in the collection. We start with the loop example.
public Set<String> getDistinctTags() {
Set<String> result = new HashSet<>();
for (Article article : articles) {
result.addAll(article.getTags());
}
return result;
}
Ok, let's see how we can solve this with the stream operations.
public Set<String> getDistinctTags() {
return articles.stream()
.flatMap(article -> article.getTags().stream())
.collect(Collectors.toSet());
}
Nice!
flatmap
helpes us flatten the tag lists into one result stream, then we use collect
to create a set to return.