📜 ⬆️ ⬇️

Java 8: Master the new level of abstraction

One of the many reasons why I like working with functional programming is the high level of abstraction. This is due to the fact that in the end we are dealing with a more readable and concise code, which undoubtedly contributes to convergence with the logic of the subject area.

This article focuses more on four things introduced in Java 8 that will help you master a new level of abstraction.



')

1. No more cycles.


I have said this before, and I will say it again. Say goodbye to the cycles, and welcome to the Stream API. Java talk days about item loops are coming to an end. With the Stream API in Java, we can say what we want to get, instead of saying how this can be achieved.

Let's consider the following example.

We have a list of articles, each of which has its own list of tags. Now we want to get the first article containing the "Java" tag.

Take a look at the standard approach.
public Article getFirstJavaArticle() { for (Article article: articles) { if (article.getTags().contains("Java")) { return article; } } return null; } 


Solve the problem using the Stream API.
 public Optional<Article> getFirstJavaArticle() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .findFirst(); } 


Pretty cool, right?

First, we use filter to find all articles that contain a Java tag, then use findFirst to get the first inclusion.

The question arises: why should we filter the entire list if we need only the first inclusion? Since the threads are ... lazy and the filter returns a stream, the calculations take place until the first inclusion is found.

I have already dedicated an article about replacing loops with the stream API . Read it if you need more examples.

2. Get rid of null checks


You may notice that in the previous example we can return Optional <Article> .

Optional is a container of an object that may or may not contain a non-zero value.

This object has some higher-order functions that eliminate the addition of duplicate if null / notNull checks, which allows us to focus on what we want to do.

Now we will improve the getFirstJavaArticle method. If no java article is found, we will be happy to receive the latest article.

Let's look at what a typical solution looks like.
  Article article = getFirstJavaArticle(); if (article == null) { article = fetchLatestArticle(); } 


And now the solution using Optional <T> .
 getFirstJavaArticle() .orElseGet(this::fetchLatestArticle); 


Looks great, doesn’t it?

Neither superfluous variables, nor if -constructions, nor any references to null . We simply use Optional.orElseGet to say what we want to get if no value is found.

Take a look at another example of using Optional . Suppose we want to get the title of the first Java article, if it is found.

Again, using a typical solution, we would have to add a null check, but ... you know what? Optional here to save this day.
 playGround.getFirstJavaArticle() .map(Article::getTitle); 


As you can see, Optional implements a higher order map function, helping us apply a function to the result, if there is one.

For more information about Optional, see the documentation .

3. Build your higher order functions.


As you can see, Java 8 comes with a ready-made set of higher-order functions, and you can work wonders with them. But why stop? And why not create your own higher-order functions?

The only thing needed to make a higher-order function is to take one of the Java functional interfaces, or the SAM-type interface, as an argument and / or return one of them.

To illustrate this, consider the following scenario.

We have a printer that can print various types of documents. Before printing, the printer should warm up, and after printing go into sleep mode.

Now we want to be able to send commands to the printer without concern for its on and off procedures. This can be solved by creating a higher order function.
 public void print(Consumer<Printer> toPrint) { printer.prepare(); toPrint.accept(printer); printer.sleep(); } 


As you can see, we use Consumer <Printer> , which is one of the functional interfaces, as an argument. We then perform this function as a step between the startup and shutdown procedures.

Now we can easily use our printer, not caring about anything else, except what we want to print.
 //    printHandler.print(p -> p.print(oneArticle)); //    printHandler.print(p -> allArticles.forEach(p::print)); 


For a more detailed example, read my article on how to create a TransactionHandler .

4. Beware of duplication. DRY principle


Writing functions is easy and fast. However, with easy code writing comes the desire for duplication.

Consider the following example.
 public Optional<Article> getFirstJavaArticle() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .findFirst(); } 


This method served us well once, but it is not universal. We need a method that will be able to find articles based on other tags and requirements in general.

It is very tempting to create new threads. They are so small and so easy to do, how can it hurt? On the contrary, small sections of the code should motivate the DRY principle further.

Let's reorganize our code. To begin with, we will make our getFirstJavaArticle function more universal — it will take a predicate as an argument to filter out articles according to what we need.
 public Optional<Article> getFirst(Predicate<Article> predicate) { return articles.stream() .filter(predicate) .findFirst(); } 


Let us now try to use this feature to get several different articles.
 getFirst(article -> article.getTags().contains("Java")); getFirst(article -> article.getTags().contains("Scala")); getFirst(article -> article.getTitle().contains("Clojure")); 


And yet, we are still dealing with duplicate code. You can see that the same predicate is used for different values. Let's try to remove these duplications through the Function interface. Now our code looks like this.
 Function<String, Predicate<Article>> basedOnTag = tag -> article -> article.getTags().contains(tag); getFirst(basedOnTag.apply("Java")); getFirst(basedOnTag.apply("Scala")); 


Fine! I would say that this code is DRY compliant, right?

I hope this article has been useful to you and has given a few ideas on what you can do at a higher level of abstraction using Java 8 and functional features.

Source: https://habr.com/ru/post/256057/


All Articles