Oogways: Advice on Optional in Java 8
Want to learn more about using Optionals in Java? Check out this post to learn more about why passing an Optional in the input is a bad idea!
Join the DZone community and get the full member experience.
Join For FreeOogway's previous talk clears all the confusions on why Optional is added to Java 8. But, PO is a dragon warrior, and he is the finest Java warrior around. He wants to know more; he wants to know it is time to use Optional. What are the best practices? So, PO went to Oogways to have a talk about Optionals.
Here is their conversation:
PO: Master Oogway, I completely understand why we use Optional — it gives the caller a hint that the output may not be available, so design your code accordingly. It is a conceptual improvement that forces the caller to tackle a scenario and the output is a less null pointer exception. But, how do we use it efficiently?
Oogways: PO, listen carefully: Optional is created to check whether a return value from a method is present or not. So, it is the only time you should use Optional and nothing else. Optional acts as a container and wraps the return value, applying different functions on it to determine if its value is present or not. In advance, if the value is present, it can take necessary functions on it in a functional way. But, the case of Optional must be used with the method return type. Using Optional<T> as a composition or passing it as an input is a very lame idea and should be avoided.
PO: What is the problem with passing Optional as an input? Suppose I have a program to search a name, so if I pass the name as Optional<String>, the developer doesn't have to perform the null check.
For more information, see the below program:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
Optional<String> findName(Optional<String> name){
return name.isPresent()?Optional.of(nameList.get(nameList.indexOf(name.get().toLowerCase()))):Optional.empty();
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
Optional<String> searchedNameOptional = optionalTest.findName(Optional.of("Shamik"));
Optional<String> searchedNameOptionalSecond = optionalTest.findName(Optional.ofNullable(null));
searchedNameOptional.ifPresent(System.out::println);
searchedNameOptionalSecond.ifPresent(System.out::println);
}
}
Here, I create a method called findName
, which takes an Optional<String>. So, the developer can check if the value is present it returns an Optional<String> or an empty Optional. There is no null check involved, and passing the Optional caller signal to the developer that is passing the parameter may be present or absent, so deal with this accordingly. I think this is a nice way to tackle input parameters. So, master, why are you are telling me that passing an Optional in the input is a bad idea?
Oogway: PO, there is a subtle conceptual error in your thinking.
You are right — Optional is used for signaling whether a value can be present or absent. But, think about who is signaling to whom. Here, the caller is signaling to the author of the code. The author is the creator of the code, and the author is very sure about what it's method input does and returns for each method. Here, signaling is meaningless. If you only pass the name, in this case, the author, it also knows that the value may be null as the default value of String is null. But, the author can take care of it, With using Optional, it becomes a bit redundant. Optional means that the developer of the code reminds themselves that passing the parameter may be present or absent, but this is just nonsense. Optional works fine for the client of the method as the client does not know what the method is doing inside. The author only knows this information by the call findName
. I can get an Optional<String >. This method may give a blank result, so I need to tackle it. But, the reverse perspective is false. There is no need to signal to the developer, who controls the implementation, what they need to do with inputs. In this case, the null check is better than Optional. Another thing is when passing Optional, you create a wrapper on a value that takes more memory space of an unnecessary, complex code. Also, the caller has to create an Optional
container, which is a break of encapsulation. The best way to represent your code is the following:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
Optional<String> findName(String name){
return name !=null?Optional.of(nameList.get(nameList.indexOf(name.toLowerCase()))):Optional.empty();
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
Optional<String> searchedNameOptional = optionalTest.findName("Shamik");
Optional<String> searchedNameOptionalSecond = optionalTest.findName(null);
searchedNameOptional.ifPresent(System.out::println);
searchedNameOptionalSecond.ifPresent(System.out::println);
}
}
In this way, the developer issued the null check. However, always remember that Optional is not an option for replacing the null or applying some fancy methods on a value like filter/map
, etc. The Optional is for signaling if a value is present or not. So, don't use it in composition or input variables just for the sake of using Optional.
PO: Now, I understand, master! Please, tell me, what are some of the operations we can perform using Optional?
Oogways: Yes, PO, now we are in a position where we can use some of Optional 's utility methods.
Sometimes, if a value is not present, in that case, you can use orElse
on the Optional. Suppose, if the name is not found, we show "NA" as the default value. In that case, we can change the findName
method to the following:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
public String findName(String name){
return Optional.ofNullable(name).map(val->nameList.get(nameList.indexOf(val.toLowerCase()))).orElse("NA");
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
String blankName = optionalTest.findName(null);
String name = optionalTest.findName("Shamik");
System.out.println("Name is :: " + blankName);
System.out.println("Name is :: " + name);
}
}
Here, I use a map function that will check to see if the name is in name List
or not. If it is not found, it will return "NA," as in orElse
, and I provide a default value as "NA."
Now, if the default value is fetched from the database or a Properties file, then we need to write a separate method, which returns the default value. In that case, we can use that separate method as a reference and pass a supplier interface in the orElseGet
method. Check out the following example:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
String findNameWithSupplier(String name){
return Optional.ofNullable(name).map(val->nameList.get(nameList.indexOf(val.toLowerCase()))).orElseGet(()->"NA");
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
String blankName = optionalTest.findNameWithSupplier(null);
String name = optionalTest.findNameWithSupplier("Shamik");
System.out.println("Name is :: " + blankName);
System.out.println("Name is :: " + name);
}
}
Optional has two utility functions called isPresent
and ifPresent
. The former returns true if a value present later takes a callback function, which will apply to the value when the value is present. So, when you see a code block like the following:
if(optional.isPresent()){
doSomething();
}
Then, you will need to replace the same with ifPresent
:
optional.ifPresent(val->doSomething())
For more, see the additional example below:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
Optional<String> findName(Optional<String> name){
return name.isPresent()?Optional.of(nameList.get(nameList.indexOf(name.get().toLowerCase()))):Optional.empty();
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
Optional<String> searchedNameOptional = optionalTest.findName(Optional.of("Shamik"));
if(searchedNameOptional.isPresent()) {
System.out.println(searchedNameOptional.get());
}
searchedNameOptional.ifPresent(System.out::println);
}
}
Here, I replaced the isPresent
with ifPresent
.
The flatmap
function works the same as the map
function, i.e. change one data structures to another data structure. But, if the return data structure holds an Optional, it does not create a nested Optional Structure Optional<Optional<T>>, it just returns Optional<T>.
For more, check out the following example:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
public void flatMapTest(String name) {
Optional<String> retName = Optional.of(name).flatMap(val->Optional.of(val));
System.out.println(retName);
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
optionalTest.flatMapTest("Shamik");
}
}
In the FlatMap
function, I deliberately returned the Optional.of()
, and as the flatMap
returns Optional<T>, it should then be Optional<Optional<String>>, but it only returns:
Optional<String> if we use map function we got Optional<Optional<String>>
We can use the filter
function on Optional, so we can filter the value based on some criteria:
package com.example.optional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class OptionalTest {
private static List<String>nameList = new ArrayList<String>();
static {
nameList.add("shamik");
nameList.add("samir");
nameList.add("swastika");
}
Optional<String> findName(Optional<String> name){
return name.isPresent()?Optional.of(nameList.get(nameList.indexOf(name.get().toLowerCase()))):Optional.empty();
}
public static void main(String[] args) {
OptionalTest optionalTest = new OptionalTest();
Optional<String> searchedNameOptional = optionalTest.findName(Optional.of("Shamik"));
searchedNameOptional.filter(val->val.contains("am")).ifPresent(System.out::println);
}
}
PO: Master, I just learned so many important functions of Optional. Is there anything left to know about Optional?
OogWay: Yes, PO. We have covered a vast majority of the things you should know, but there is one more thing: why Optional is not serializable. However, I think this enough on Optionals for one day. I will give you the answer at a later date. I want you to think about it on your own, first.
PO: Ok. Thank you, Master.
Opinions expressed by DZone contributors are their own.
Comments