
This is the third 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.
The second article describes the methods of the Optional class as it appeared in Java 8.
This article describes the class methods introduced in Java 9.
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.
The source code examples for this and other articles on this topic can be found in the
project on GitHub .
In Java 9, three new methods have been added to the Optional class: stream (), ifPresentsOrElse (), and or ().
We begin our consideration.
Stream () method: take everything you can
This method is useful if you have a List <Optional <T >>. Each element of the list (as defined in the first article of the series) is a case, or a container, which may contain a “real” element or be empty. If you need the easiest way to get all “real” elements from this list, the stream () method will help you.
Imagine such a situation.
')
Programmer Ivan works in a project in remote access mode (remote). This gave him the opportunity to move to live in a house in the village. In his garden, he planted several rows of vegetables. (If you, dear reader, are a summer resident practitioner, be indulgent to the assumptions in the modeling of the subject area).
Every morning, Ivan goes to his garden and gathers ripe fruit from the bushes. On each bush, the fruit may ripen during the night, or may not ripen. Therefore, we can simulate the harvest of each bush using Optional. For simplicity, we will use the String class as T.
Thus, the crop of vegetables, collected by Ivan every morning, we can model as List <Optional <String >> getTomatoBeds ().
Suppose we want to get a list of fruits (of course ripe) in the form of an array.
Without the use of stream (), we would have to write a for ... loop for this purpose, sort through all the "cases" in it, write the "real" elements to the list and then rewrite them into an array.
In Java 8 for an array of ripened vegetables can be obtained as follows:
String[] result = tomatoGarden.getTomatoBeds() .stream() .filter(Optional::isPresent) .map(Optional::get) .toArray(String[]::new);
And in Java 9, this can be made even shorter and more elegant:
String[] result = tomatoGarden.getTomatoBeds() .stream() .flatMap(Optional::stream) .toArray(String[]::new);
Replacing two lines with one was made possible by calling the new stream () method inside flatMap. This method, according to the
documentation, does the following: "If an object is present, it returns a sequential stream (stream) containing only this object, otherwise it returns an empty stream."
In most cases, we use the stream () method to process large sequences of data. In the case of Optional, we encountered stream () to handle a sequence of a maximum of one element.
To consolidate another example:
public void testOptionalStreamBase() { Optional<String> opFilled = Optional.of("Filled"); assertEquals(1, opFilled.stream().count()); Optional<String> opEmpty = Optional.empty(); assertEquals(0, opEmpty.stream().count()); }
Metod ifPresentOrElse (): If not, add!
This new method has closed the hole that remained in Java 8 and which had to be compensated by a combination of calls to the ifPresent (...) and orElse (...) methods.
Let's look again at the documentation for the new method: “If an object is present, the specified action is performed with it, otherwise an action is performed with the missing object.”
In other words, the method allows internally to process both the filled and empty “case”.
So let's continue the story with Ivan. Ivan goes out to his garden every morning, gathers ripe vegetables and puts them in a salad. To simulate this fact, we will use the setValue (String s) method. But if the beds did not grow, he has to get canned vegetables from the can. For this we will use the setDefault () method.
Here is their implementation:
private void setValue(String s){ veg = s;} private void setDefault(){ veg = CANNED_FOOD;}
The variable of the veg class is what Ivan will find in a salad bowl.
And now let's check how ifPresentOrElse () works with the test:
@Test public void testOptionalStreamIfPresentOrElse() { Optional<String> optFilled = Optional.of(TOMATO); optFilled.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(TOMATO, veg); Optional<String> optEmpty = Optional.empty(); optEmpty.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(CANNED_FOOD, veg); }
As we can see, with the help of this model, Ivan, though he did not find a fresh vegetable in the garden, but replaced it with a vegetable from a tin can. And everything is inside the call of one method!
Method or (): stubbornly looking for your happiness!
The attentive reader probably noticed that in the previous example, Ivan examined only the first bush. And how to simulate a situation if there are many bushes?
In this case, the or () method will help us. Let's look at the documentation again: “If an object is present, it returns an Optional that contains it, otherwise it returns an Optional created by the function.”
In other words, using or () you can build processing chains that will analyze “cases” (Optional <T>) until the first non-empty element is encountered.
In the following example, optBed1, 2, 3 model individual bushes.
@Test public void testOptionalOr1() { Optional<String> optBed1 = Optional.empty(); Optional<String> optBed2 = Optional.of(TOMATO); Optional<String> optBed3 = Optional.of(CUCUMBER); String res = optBed1 .or(()->optBed2) .or(()->optBed3) .or(()->getDefault()) .get(); assertEquals(res, TOMATO); }
Congratulations to Ivan, with the help of the new method or (), this time he advanced further, found a fresh tomato and he would not have to eat vegetables from a tin can.
Full text of the test public class VegetableGardenTest { private static final String CANNED_FOOD = "Canned food"; private static final String MY_GARDEN = "My garden"; public static final String TOMATO = "Tomato"; public static final String CUCUMBER = "Cucumber"; private String[] EXPECTED_RESULT; private VegetableGarden tomatoGarden; private String veg; @Before public void setUp() throws Exception { EXPECTED_RESULT = new String[]{"A1", "A3", "A6"}; tomatoGarden = new VegetableGarden("A1", null, "A3", null, null, "A6"); } @Test public void testOptionalStreJava8() { String[] result = tomatoGarden .getTomatoBeds() .stream() .filter(Optional::isPresent) .map(Optional::get) .toArray(String[]::new); assertArrayEquals(EXPECTED_RESULT, result); } @Test public void testOptionalStreamJava9() { String[] result = tomatoGarden .getTomatoBeds() .stream() .flatMap(Optional::stream) .toArray(String[]::new); assertArrayEquals(EXPECTED_RESULT, result); } @Test public void testOptionalStreamBase() { Optional<String> opFilled = Optional.of("Filled"); assertEquals(1, opFilled.stream().count()); Optional<String> opEmpty = Optional.empty(); assertEquals(0, opEmpty.stream().count()); } @Test public void testOptionalStreamIfPresentOrElse() { Optional<String> optFilled = Optional.of(TOMATO); optFilled.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(TOMATO, veg); Optional<String> optEmpty = Optional.empty(); optEmpty.ifPresentOrElse(this::setValue, this::setDefault); assertEquals(CANNED_FOOD, veg); } @Test public void testOptionalOr1() { Optional<String> optBed1 = Optional.empty(); Optional<String> optBed2 = Optional.of(TOMATO); Optional<String> optBed3 = Optional.of(CUCUMBER); String res1 = optBed1 .or(()->{return optBed2;}) .or(()->optBed3) .or(this::getDefault) .get(); assertEquals(res1, TOMATO); } @Test public void testOptionalOr2() { Optional<String> optBed1 = Optional.empty(); Optional<String> optBed2 = Optional.empty(); Optional<String> optBed3 = Optional.empty(); String res1 = optBed1 .or(()->optBed2) .or(()->optBed3) .or(()->getDefault()) .get(); assertEquals(res1, CANNED_FOOD); } private void setValue(String s){ veg = s;} private void setDefault(){ veg = CANNED_FOOD;} private Optional<? extends String> getDefault(){return Optional.of(CANNED_FOOD);}; }
Innovations in the Optional class in Java 9 have increased its power and attractiveness for everyday use in Java projects. And yet, my expectations did not come true.
The fact is that when processing objects with a dynamic structure, if an object cannot be created or obtained from a resource, it is important not only to prevent NullPointerException, but also to find out the reason for the failure. And Java 9 does not offer a good way to solve this problem.
Therefore, I would venture to suggest in the
next article my own solution to this important practical problem.
Illustration:
ThePixelman