I have recently been spending some time getting familiar with the Stream API, also introduced in Java 8, and a lot of the terminating methods of the Stream API returns an Optional<T>...
At first encounter with Optional<T>, my first thought was asking what real advantage does this offer? How is it different from checking if an object is null before calling its methods?
The Optional<T> has the isPresent() and get() methods which can be used to check if the enclosing object is null or not, and then retrieve it. Like so:
Optional<SomeType> someValue = someMethod(); if (someValue.isPresent()) { // check someValue.get().someOtherMethod(); // retrieve and call }
But exactly the same thing can be done with plain null checks with the code looking almost the same:
SomeType someValue = someMethod(); if (someValue != null) { someValue.someOtherMethod(); }
What then is Optional<T> good for?
If you are not already familiar with Optional<T>, as described above, it is a container object...that wraps around an object of type T, and its primary purpose of existence is to have a safer way of interacting with the enclosed object T in situations where T could have been returned as a null value: thus preventing the world famous NullPointerException of Java.
But using Optional<T>'s isPresent() together with get() methods is not idiomatic Java 8, and you would not be getting the full benefits of this addition to the language if you only use it that way.
You get the real benefits out of Optional<T> if you see it as some sort of middle man, a mediator, and not a plain object wrapper, between a method that needs to use the value T and T itself...
So let's say, you have a value returned as type T, you have a method that would use this value, and then, you have Optional<T> in the middle to ensure this interaction goes well without things blowing up in our faces.
Thus, as a mediator, Optional<T> can take care of having the value of T supplied to your method, and in case of T being null, makes sure things don't blow up. Apart from making things safe when using a value T which could be null, you can also have Optional<T> perform other actions, like execute a code block, provide a substitute etc in case T turns out to be null. These extra capabilities are what make Optional<T> such a nice addition to the language.
I would quickly run through how these can be effectively put to use.
The examples would include usage of the Lambda expressions, method reference etc, to extract the first word starting with the letter "L" in a stream of words. This trivial example comes in handy as the result of the operation is returned as an Optional<String>. If not familiar with Lambda, method reference and the functional additional to Java with the 1.8 release, you may not find the examples easily accessible, so I suggest you watch this video on the topic first.
So let us see how Optional<T> can be put to more novel and effective use.
Use ifPresent()
This is for the use case, shown above, but does not use the combination of isPresent() and get(). Optional<T> but uses ifPresent() which can take a lambda expression; a method to which the value of T can be passed if it is not null. It is used thus:
Stream<string> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa"); Optional<string> firstLetterIsL = names .filter(name -> name.startsWith("L")) .findFirst(); firstLetterIsL.ifPresent(name -> { String s = name.toUpperCase(); System.out.println("The first name to begin with L is "+ s); });
This finds the first word starting with the alphabet L. An operation that could result to nothing being found, in case no word in the stream starts with an L. In case a word starting with L is found, the word is added to the supplied lambda expression, which in the example above, capitalizes it and write the value to the console.
Use map()
What if you want to retrieve a return value from the operation on the Optional<T>? You use map() instead. E.g:
Stream<string> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa"); Optional<string> firstLetterIsL = names .filter(name -> name.startsWith("L")) .findFirst(); Optional<string> lNameInCaps = firstLetterIsL.map(String::toUpperCase);
Usage of map on Optional<T> results in the return object as another Optional, i.e. LnameInCaps as in above; which makes sense since the operation passed into map() may also result in a null value.
Use orElse()
You use orElse() in the situation where you want to have an alternative value in case T is null. So instead of the pre Java 8 where ternary operation may have been put to use, in idiomatic Java 8, you have:
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa"); Optional<String> firstLetterIsQ = names .filter(name -> name.startsWith("Q")) .findFirst(); String alternate = firstLetterIsQ.orElse("Nimrod"); System.out.println(alternate); //prints out "Nimrod"
Use orElseGet()
orElseGet() method is similar to orElse(), but instead of providing an alternative, should in case null is encountered, you provide code which can be used to generate the alternative value . For example:
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa"); Optional<String> firstLetterIsQ = names .filter(name -> name.startsWith("Q")) .findFirst(); String alternate = firstLetterIsQ.orElseGet(() -> { // perform some interesting code operation // then return the alternate value. return "Nimrod"; }); System.out.println(alternate);
Use orElseThrow()
orElseThrow() comes in handy when you want to decide which exception to throw in case a null value is encountered. For example:
Stream<String> names = Stream.of("Lamurudu", "Okanbi", "Oduduwa"); Optional<String> firstLetterIsQ = names .filter(name -> name.startsWith("Q")) .findFirst(); firstLetterIsQ.orElseThrow(NoSuchElementStartingWithQException::new);
Which throws a NoSuchElementStartingWithQException
With the highlighted capabilities of Optional<T> you can get to deal with nullable values in a more novel ways, not possible prior to Java 8.
So far so good the instances of Optional<T> used are returned from stream operations. What if you like these benefits of having Optional<T> and you want your own code to return it instead of nulls? How do you create an Optional<T> then?
Simple. You have 3 methods for creating an Optional<T> type:
Optional<SomeType> getSomeValue() { // returns an empty Optional type; return Optional.empty(); } Optional<SomeType> getSomeValue() { SomeType value = ...; // With this method, value cannot be null // or else an exception would be thrown return Optional.of(value); } Optional<SomeType> getSomeValue() { SomeType value = ...; // With this method, value may be null // if null, then Optional.empty is returned return Optional.ofNullable(value); // usage Optional<SomeType> someType = getSomeValue();
No comments:
Post a Comment