-
The functional style introduced in Java 8 helps us reduce the gap between business logic and code. It allows us to tell the story in a natural flow at a higher level. Instead of saying how you want to do it, you can say what you want to do.
-
The code becomes cleaner and more concise.
-
High order functions allow us to:
- Send functions to other functions
- Create functions inside other functions
- Return functions from other functions
This is a big win for Java, where we need to send, create, and return objects to do this. We will be able to write code that is more reliable, focused, and easier to reuse.
-
Thanks to lambdas, we can do lazy calculations. When a lambda expression is sent as a method argument, the compiler will evaluate it when it is called in the method. This is different from normal method arguments, which are evaluated immediately.
-
Lambdas make writing unit tests fun. They allow us to create lightweight tests that are clean, small in size, and quick to write. We can root out the code under test using lambdas. This allows us to test how all kinds of scenarios will affect the code.
-
New patterns to learn.
-
And much more!
break
, dramatically change the behavior of the loop, forcing us to understand not only what the code is trying to achieve, continue
but return
also to understand how the loop works. Now we'll take a look at how we can transform loops into more concise and readable code.
Let the coding begin!
We will work with articles. An article has a title, 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 example will contain a traditional solution using loops and a solution using the new features of Java 8. In the first example, we want to find the first article in the collection with the tag “Java”. Let's take a look at a solution using a 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, isn't it? We first use the operation filter
to find all articles with the tag “Java”, then we use findFirst()
to get the first occurrence. Since streams are lazy and the filter returns a stream, this approach will only process elements until it finds the first match. Now let's get all the articles tagged “Java” instead of just the first one. First the solution using loops.
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 an operation collect
to shorten the resulting stream, rather than declaring a collection and explicitly adding the entries that match. So far so good. Time for examples that will make the Stream API really shine. Let's group all articles by author. As usual, we start by solving it using loops:
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 to this problem using stream operations?
public Map<String, List<Article>> groupByAuthor() {
return articles.stream()
.collect(Collectors.groupingBy(Article::getAuthor));
}
Amazing! By using an operation groupingBy
and a method reference getAuthor()
, we get clean and readable code. Now let's find the rest of the tags used in the collection. Let's start with a loop example:
public Set<String> getDistinctTags() {
Set<String> result = new HashSet<>();
for (Article article : articles) {
result.addAll(article.getTags());
}
return result;
}
Okay, let's take a look at how we can solve this using stream operations:
public Set<String> getDistinctTags() {
return articles.stream()
.flatMap(article -> article.getTags().stream())
.collect(Collectors.toSet());
}
Cool! flatmap
helps us flatten the list of tags into a single result stream, which we then use collect
to create the return set.
GO TO FULL VERSION