📜 ⬆️ ⬇️

Object in a case or Optional in Java 8 and Java 9. Part 2: “How it is done in Java 8”

Object in the case
Optional class is devoted a lot of articles and tutorials, including this and this one on Habré.
Most of them tell how methods of this class are called. In this tutorial I emphasize why, why, in what cases it is possible (or rather even necessary) to apply one or another class method. I think this is very important, because, as the survey showed after the first article in this tutorial, not all Java programmers are in the taste of using all the power of the methods of this class.
For a better explanation of the class methods, I will use more complex and illustrative examples than in most other tutotials — a coffee maker, a filtration unit, a mixer, etc.
This is the second article in the series on the use of the Optional class when processing objects with dynamic structure. The first article was about ways to avoid NullPointerException in situations where you can’t or don’t want to use Optional.
In this article, we will look at all the class methods as provided by Java 8. The class extensions in Java 9 are discussed in the third article in this series. The fourth article is devoted to the necessary (from the point of view of the author) addition to this class.
Well, in the fifth article I tell about where the Optional should be used inside the class, summarize and give every reader who has read the series to the end, a valuable gift.
In this tutorial there will be a lot of source code, including Junit tests. I share the opinion of some of my colleagues on the pen that reading the test code helps to better master the material. You'll find all the sources in my GitHub project .
So, in the first article of this series I tried to consider approaches that you can use when implementing objects with dynamic structure and promised to justify why the Optional almost always works better than other approaches in this situation. Let's start fulfilling promises. Let's start with the definition.

What is Optonal?


Before we get to the concrete example, let's try to answer the question, what is Optional?

I would venture to give my own visual definition. In the first approximation, Optional is a software analogue of a case of a physical object, for example, glasses. Whether the object is inside the case, you can find out using the isPresent () method. If it is there, you can take it using the get () method. In general, approximately as shown in the title picture of the series.
')
So:
In the first approximation, Optional is a case for some object.

Use to a minimum


In our first example, we try using Java 8 Optional to simulate the operation of a device that combines a drinking water faucet and a boiler.
His diagram is shown in the picture below:



As you can see, the device needs water and electricity to operate. At the exit, it can produce raw or boiled water.

Thus, the input of this device can be described with the following interface:

public interface IBoilerInput { void setAvailability(boolean waterAvailable, boolean powerAvailable); } 

And the output is like this:

 public interface IBoilerOutput { Optional<CupOfWater> getCupOfWater(); Optional<CupOfBoiledWater> getCupOfBoiledWater(); } 

Since (depending on the input data) the device may or may not give out raw and boiled water, we present the result of calling get ... with the help of Optional.

The behavior of the device as a whole describes an interface that combines input and output methods.

 public interface IBoiler extends IBoilerInput, IBoilerOutput {} 

Classes representing the varieties of water are of little interest to us, so we implement them in a minimal way. This is the class for serving raw water:

 public class CupOfWater { public CupOfBoiledWater boil() {return new CupOfBoiledWater();} } 

We will assume that a portion of boiled water is a new object, different from raw water. Therefore, we will present it as a separate class:

 public class CupOfBoiledWater {} 

So, the task is set. In the best traditions of TDD (Test-Driven Development), we first write a test to check whether we simulated the behavior of our simple device:

JUnit test Boiler1Test
 public class Boiler1Test { private IBoiler boiler; @Before public void setUp() throws Exception { boiler = new Boiler1(); } @Test public void testBothNotAvailable() { boiler.setAvailability(false, false); assertFalse(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testPowerAvailable() { boiler.setAvailability(false, true); assertFalse(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testWaterAvailable() { boiler.setAvailability(true, false); assertTrue(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testBothAvailable() { boiler.setAvailability(true, true); assertTrue(boiler.getCupOfWater().isPresent()); assertTrue(boiler.getCupOfBoiledWater().isPresent()); } } 

Our test verifies that the appliance actually delivers raw water if water is supplied to the inlet, regardless of the presence of electricity. But the device produces boiled water only if there is both water and electricity.

Before moving on to implementation, stop for a moment and think through your head or even behind the keyboard, how would you program this solution within the framework of the approaches discussed in the first article of the series:
With a pair of has ... get ...
C using the return array or sheet of values
With the help of a sign of activity issued to the outside product.
If you really tried to imagine it, and even better, try to program the solution of the problem within these approaches, you will certainly appreciate the simplicity and elegance that Java 8 Optional brings to our programming life.

Look at my, probably not the optimal solution:

 public class Boiler1 implements IBoiler { private boolean waterAvailable; private boolean powerAvailable; @Override public void setAvailability(boolean waterAvailable, boolean powerAvailable) { this.waterAvailable = waterAvailable; this.powerAvailable = powerAvailable; } @Override public Optional<CupOfWater> getCupOfWater() { return waterAvailable ? Optional.of(new CupOfWater()) : Optional.empty(); } @Override public Optional<CupOfBoiledWater> getCupOfBoiledWater() { if(!powerAvailable)return Optional.empty(); return getCupOfWater().map(cupOfWater->cupOfWater.boil()); } } 

Pay attention to the last line of listing where the map () method from the Optional class is used. This way you can build processing chains. If it turns out at one of the links in the chain that no further processing is possible, the entire chain will return an empty response.

The results of our boiler model depend on external conditions, which were set using Boolean variables. But in the majority of practically interesting problems, not simple variables are fed into the input, but objects. Including those that can be null.

Consider how you can apply Optional if the behavior is not determined by Boolean variables, but by “old-mode” objects that admit zero values.
Let the input of our boiler a slightly different model than in the first example is defined as follows:

 public interface IBoilerInput2 { void setAvailability(@Nullable CupOfWater water, boolean powerAvailable); } 

The zero value of the water object means that water does not flow into the device from the water supply system.
Then the behavior of the device as a whole is defined by the following interface:

 public interface IBoiler2 extends IBoilerInput2, IBoilerOutput {} 

As in the previous example, we define tests that verify the correctness of our implementation:

JUnit test Boiler2Test
 public class Boiler2Test { private IBoiler2 boiler; @Before public void setUp() throws Exception { boiler = new Boiler2(); } @Test public void testBothNotAvailable() { boiler.setAvailability(null, false); assertFalse(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testPowerAvailable() { boiler.setAvailability(null, true); assertFalse(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testWaterAvailable() { boiler.setAvailability(new CupOfWater(), false); assertTrue(boiler.getCupOfWater().isPresent()); assertFalse(boiler.getCupOfBoiledWater().isPresent()); } @Test public void testBothAvailable() { boiler.setAvailability(new CupOfWater(), true); assertTrue(boiler.getCupOfWater().isPresent()); assertTrue(boiler.getCupOfBoiledWater().isPresent()); } } 

If we compare these tests with tests for the boiler of the first model, we will see their very similarity. Checking the results of the same tests from different sets is the same. Well, the input differs in that instead of true for the water source, we submit the object, and instead of false, -null.

And here is the implementation itself:

 public class Boiler2 implements IBoiler2 { @Nullable private CupOfWater water; private boolean powerAvailable; @Override public void setAvailability(@Nullable CupOfWater water, boolean powerAvailable) { this.water = water; this.powerAvailable = powerAvailable; } @Override public Optional<CupOfWater> getCupOfWater() { return Optional.ofNullable(water); } @Override public Optional<CupOfBoiledWater> getCupOfBoiledWater() { if(!powerAvailable)return Optional.empty(); return getCupOfWater().map(cupOfWater->cupOfWater.boil()); } } 

As we can see, the Optional.ofNullable () method allows you to elegantly “put” a dangerous object with a potentially zero value into a case. If the object has a zero value, the case will be empty. Otherwise, it contains the object we need.

It is time to sum up the first results and formulate the first rules for minimal use of the Optional:
If your method returns an object that may or may not be present, you “stack” it in the Optional. When laying you use the following rules:
ConditionClass method used
Object missingOptional.empty ()
The object is present and not exactly null.Optional.of (...)
The object is present, but may be nullOptional.ofNullable (...)

Whether the object is in a case, you define using the isPresent () method. And if the check is positive, you get the object out of the case with get ().

So we mastered using Optional so that we no longer use null as the return result.

But we will not stop there.

Let us now consider another, not so rare situation, when a certain resource is represented by the main and reserve element.

Well, when there is a stash ...


Zanachka is a common definition for a backup resource. Let us distract from the emotional side of this term and consider the technical side of the question.

In technical systems, resources of the same kind are often available in more than one way.

In the following example, we consider a simple water supply fixture. This is how irrigation devices used by summer residents are arranged. Rainwater is collected in a special container, which is then consumed first. If it is not there or it is over, water is consumed from the water supply system.

We will not complicate the task with unnecessary details about the incomplete filling of the rain tank and its size, and simply use the familiar CupOfWater class again.

The input of such a device is described as follows:

 public interface IWaterDispenserInput { void setAvailability(@Nullable CupOfWater firstPortion); } 

If rainwater is not collected, then at the entrance we have a zero object, otherwise - a normal object.

The output of the device is described by the following interface:

 public interface IWaterDispenserOutput { CupOfWater getCupOfWater(); } 

Note that at the output we have a CupOfWater object and not an Optional. We do so in order to more clearly show the mechanism of interest. After you, dear readers, understand it, you can easily reprogram an example and receive an Optional output.

The behavior of the device as a whole is determined by the combination of these interfaces:

 public interface IWaterDispenser extends IWaterDispenserInput, IWaterDispenserOutput {} 

As in the previous examples, we first prepare tests to test the behavior of our implementation:

JUnit test WaterDispenser1Test
 public class WaterDispenser1Test { private IWaterDispenser waterDispenser; @Before public void setUp() throws Exception { waterDispenser = new WaterDispenser1(); } @Test public void testMainAvailable() { waterDispenser.setAvailability(new CupOfWater()); assertNotNull(waterDispenser.getCupOfWater()); } @Test public void testMainNotAvailable() { waterDispenser.setAvailability(null); assertNotNull(waterDispenser.getCupOfWater()); } } 

Our expectations are as follows: the device produces water regardless of whether the tank with rain water is filled or not, because in the latter case the water will be taken from the “reserve” (water supply system).

Consider now the implementation:

 public class WaterDispenser1 implements IWaterDispenser{ @Nullable private CupOfWater mainCup; @Override public void setAvailability(@Nullable CupOfWater mainCup) { this.mainCup = mainCup; } @Override public CupOfWater getCupOfWater() { return Optional.ofNullable(mainCup).orElse(new CupOfWater()); } } 

As we see, the orElse method has been added to the ofNullable () method. If the first element gives an empty Optional (no rainwater accumulated), the second method will add an object from itself. If the first method gives a non-empty Optional, the second method will simply pass it through itself and the tap water will remain untouched.

This implementation assumed the presence of a backup object. If you need to create an object before this (in our case, pump up water), you can use the orElseGet () method with the Supplier type parameter:

 public class WaterDispenser2 implements IWaterDispenser{ @Nullable private CupOfWater mainCup; @Override public void setAvailability(@Nullable CupOfWater mainCup) { this.mainCup = mainCup; } @Override public CupOfWater getCupOfWater() { return Optional.ofNullable(mainCup).orElseGet(()->new CupOfWater()); } } 

Do not let the genie out of the bottle


In some cases, restrictions on your API do not allow you to use Optional as a return value.

Suppose that our interface is defined in such a way that the client always expects an object at the output of our function. If the requested resource is not present at the time of the request, and we do not want to return null, we have only one tool left - throw Exception. Thus, we do not release the genie from the bottle - we do not allow the released zero object to turn into the client code NullPoiner Exception.

Can Java 8 Optional help us in this case? Yes maybe.
But before considering the solution, we will prepare a test verifying the correctness of its work:

 @Test (expected = IllegalStateException.class) public void testMainNotAvailable() { waterDispenser.setAvailability(null); waterDispenser.getCupOfWater(); fail("This code line must be not reached"); } 

And here is the solution:

 public class WaterDispenser3 implements IWaterDispenser{ @Nullable private CupOfWater mainCup; @Override public void setAvailability(@Nullable CupOfWater mainCup) { this.mainCup = mainCup; } @Override public CupOfWater getCupOfWater() { return Optional.ofNullable(mainCup).orElseThrow(()->new IllegalStateException("Resource not available")); } } 

I think many readers will not be convinced by this decision. In fact, how is it better to check for null using if?

The main argument in favor of this decision is the possibility of building chains of functional calls in this way. However, the chain may fail with Exception. In the fourth article of this series, I venture to offer my solution to the problem of handling Exception in such chains of functional calls.

The time has come to formulate a new group of rules on using Optional for the case when we have several alternatives for creating a dynamic object:
If you have two or more alternatives for creating a dynamic object, use the following rules:
ConditionClass method used
An alternate object is present.orElse (...)
An alternate object must first be created (for example, retrieved from a repository)orElseGet (() -> ...)
Alternative resource dried up (throw exception)orElseThrow (() -> new IllegalStateException (...))


So far, we have considered using Optional at the stages of creating and using objects with dynamic structure. We now consider the question of how Optional can help us in the transformation of such objects.

Using Optional in Converters


A transformer (Transformer) receives an object as input and either modifies it or converts it into some other object. In our case, since we are limited to using Optional, we always have an Optional as an input object. Recall that this can be imagined as a case or container in which the object is located or not.

You can convert it either to a “real” object of any type, or to a new case with a new object.

At the abstract level, all variants of such a transformation can be expressed in the form of the three formulas below:

T t = f1 (Optional <T> opt)

U u = f2 (Optional <T> opt)

Optional <U> = f3 (Optional <T> opt)

Candidates for the roles of the f1, f2, and f3 transformation functions — methods from the Optional class are presented in this table:
Candidates for the role of f1Candidates for the role of f2Candidates for the role of f3
filter ()map ()flatMap ()
orElse ()
map ()orElseGet ()


In the previous posts of this cycle, we have already reviewed most of these methods. Only filter and flatMap remained unreviewed.

Below we consider examples of the use of these methods.

Filtering (using the filter method)


In the following example, we will consider using the filter () method which returns an object only if the case is not empty and the object it contains satisfies some criteria.
In our case, as an object, we will use a portion of water in the tank to collect irrigation of the suburban area. Without going into the analysis of physical and chemical features, we will assume that the collected water can either be clean (meet the criteria) or not.
The maximum simplified diagram of the device is shown in the figure below.



We simplify the behavior of our device as much as possible, reducing everything to a question: whether a portion of water is given out in this or that case or not. After this simplification, the semantics of the instrument behavior can be described by this table:



You can find the complete codes for this example in the project on GitHuB mentioned at the beginning of the article in package eu.sirotin.example.optional4

First we will get acquainted with the class representing collected rainwater:

 public class RainWater { private final boolean clean; public RainWater(boolean clean) { this.clean = clean; } public boolean isClean() { return clean; } } 

As you can see, using the isClean () method, you can find out whether the collected water is clean or not.

This class is used as an input parameter in our device.
The same object but in the “case” is used at the exit of the device.

 public interface IRainWaterDispenserInput { void setAvailability(@Nullable RainWater rainWater); } 

 public interface IRainWaterDispenserOutput { Optional<RainWater> getRainWater(); } 

And the full behavior of the device is described by a composite interface:

 public interface IRainWaterDispenser extends IRainWaterDispenserInput, IRainWaterDispenserOutput {} 

And again we will first prepare a test to verify the correctness of modeling the behavior of our device. It is not difficult to see that the expectations in the tests below completely correspond to the behavior table presented above.

JUnit test RainWaterDispenser1Test
 public class RainWaterDispenser1Test { private IRainWaterDispenser rainWaterDispenser; @Before public void setUp() throws Exception { rainWaterDispenser = new RainWaterDispenser1(); } @Test public void testRainWaterAvailableAndClean() { rainWaterDispenser.setAvailability(new RainWater(true)); assertTrue(rainWaterDispenser.getRainWater().isPresent()); assertTrue(rainWaterDispenser.getRainWater().get().isClean()); } @Test public void testWaterNotAvailable() { rainWaterDispenser.setAvailability(null); assertFalse(rainWaterDispenser.getRainWater().isPresent()); } @Test public void testRainWaterAvailableNotClean() { rainWaterDispenser.setAvailability(new RainWater(false)); assertFalse(rainWaterDispenser.getRainWater().isPresent()); } } 

Now let's get down to the implementation of our class with the help of Optional.
Here is its full text:

 public class RainWaterDispenser implements IRainWaterDispenser{ @Nullable private RainWater rainWater; @Override public void setAvailability(@Nullable RainWater rainWater) { this.rainWater = rainWater; } @Override public Optional<RainWater> getRainWater() { return Optional.ofNullable(rainWater).filter(RainWater::isClean); } } 

The last line shows the use of the filter () method. As a criterion, the value returned by the isClean () method of the object is used.

Notice also the use of the methods ofNullable () and filter () in the call chain. Doesn't it look very elegant?

Transformation - (using the flatMap method)


Suppose that the device described in the previous example is replaced by another, capable of purifying contaminated rainwater.

Its most simplified diagram is shown below.



And the behavior of the device is described by this semantic table:



If we compare this and the previous table, we will see the obvious advantage of the new device: it gives out clean water even if polluted rainwater has entered the inlet.
As always, let's start with the interfaces describing the input and output of the device:

 public interface IRainWaterCleanerInput { void setAvailability(@Nullable RainWater rainWater); } 

 public interface IRainWaterCleanerOutput { Optional<CupOfWater> getCleanedWater(); } 

Let's prepare a test verifying whether the device implements the expected behavior from it:

JUnit test RainWaterCleanerTest
 public class RainWaterCleanerTest { private IRainWaterCleaner rainWaterDispenser; @Before public void setUp() throws Exception { rainWaterDispenser = new RainWaterCleaner(); } @Test public void testRainWaterAvailableAndClean() { rainWaterDispenser.setAvailability(new RainWater(true)); assertTrue(rainWaterDispenser.getCleanedWater().isPresent()); } @Test public void testWaterNotAvailable() { rainWaterDispenser.setAvailability(null); assertFalse(rainWaterDispenser.getCleanedWater().isPresent()); } @Test public void testRainWaterAvailableNotClean() { rainWaterDispenser.setAvailability(new RainWater(false)); assertTrue(rainWaterDispenser.getCleanedWater().isPresent()); } } 

Well, now consider the class itself:

 public class RainWaterCleaner implements IRainWaterCleaner { @Nullable private RainWater rainWater; @Override public void setAvailability(@Nullable RainWater rainWater) { this.rainWater = rainWater; } @Override public Optional<CupOfWater> getCleanedWater() { return Optional.ofNullable(rainWater).flatMap(w->Optional.of(new CupOfWater())); } } 

Using the flatMap () method is shown in the last line. Unlike the map () method, this method does not return the object itself, but a case (container), which may be empty.

Using Optional in object consumers (Consume)


In the first example, we looked at the use of the isPresent () method, which allows us to determine if an object is in a case. In case further processing is supposed only in case of its presence, instead of isPresent (...) it is more expedient to use ifPresent (...)

This method does not return any value, but allows you to process an object in a case, if it is there. If it's not there, nothing happens.

Consider its effect on the example of another device, which is a complication of the previous one due to an additional function. The new version of the device can not only purify rainwater from pollution, but also mix it with certain additives. As in the previous examples, we will not be interested in the details about these additives.

The device is shown in the figure below:



First, we define a new class representing the result of mixing:

 public class MixedWater extends CupOfWater { public MixedWater(CupOfWater water) {} } 

The output of the device is determined by this interface:

 public interface IMixerOutput extends IRainWaterCleanerOutput { Optional<MixedWater> getMixedWater(); } 

We use the interface from the previous example as input. Then the full input and output of the device is determined by such a joint interface:

 public interface IMixer extends IRainWaterCleanerInput, IMixerOutput {} 

The behavior of the device is similar to the behavior of the previous device, only instead of purified rainwater, we get purified rainwater with the desired additives.

Let's make a test to check the correctness of the behavior of our device:

JUnit test MixerTest
 public class MixerTest { private IMixer mixer; @Before public void setUp() throws Exception { mixer = new Mixer(); } @Test public void testRainWaterAvailableAndClean() { mixer.setAvailability(new RainWater(true)); assertTrue(mixer.getMixedWater().isPresent()); } @Test public void testWaterNotAvailable() { mixer.setAvailability(null); assertFalse(mixer.getMixedWater().isPresent()); } @Test public void testRainWaterAvailableNotClean() { mixer.setAvailability(new RainWater(false)); assertTrue(mixer.getMixedWater().isPresent()); } } 

And here is the implementation of the main class:
 public class Mixer extends RainWaterCleaner implements IMixer{ private MixedWater result = null; @Override public Optional<MixedWater> getMixedWater() { super.getCleanedWater().ifPresent(this::mix); return Optional.ofNullable(result); } private void mix(CupOfWater water) { result = new MixedWater(water); } } 

Let's take a closer look at using the ifPresent () method. As we can see, the method from our mix () class is used as the input parameter of the method. He, in turn, expects a CupOfWater object as an input parameter. Notice that a case with an object of this type is returned by the getCleanedWater () method.

Let us formulate the rules for using Optional in consumers (clients).
If the processing of a potentially empty object will be done only in the positive case (the object is not empty) - use the IfPresent (...) method.
Otherwise, you can find out whether the object lies inside the case using the isPresent () method. If it is there, you can take it using the get () method.

Well, these are all the examples that I wanted to consider with reference to the Optional class in Java 8.

But our conversation about this class is not over yet. In the following articles, I will discuss the innovations in this class in Java 9, as well as some of its drawbacks and limitations.
Transition to the third article of this series.
Illustration: ThePixelman

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


All Articles