Writing in the front
An interesting feature introduced from Java 8 is the Optional class. The Optional class mainly addresses the infamous NullPointerException, which I won’t go into detail about, but is certainly something every Java programmer knows very well. The full path to Optional is java.util.Optional, which is used to avoid if (obj! = null) {} this paradigm of code, can adopt the style of chain programming. In addition, the filter method provided in Optional can determine whether the object meets the condition and return the object only when the condition is met. The map method can modify the properties of the object before returning the object. Be sure to follow and like if you think you’re getting something.
The article directories
-
- Writing in the front
- Optional use
- Optional constructor
- Optional common function
- How do you use Optional
- Optional Best Practices
Optional use
Essentially, Optional is a wrapper class that contains Optional values, which means that the Optional class can contain objects or be empty. Remember that Optional is a powerful step towards functional programming in Java, and helps to implement it within the paradigm. But Optional is clearly more than that. As we know, any call to an object method or property can result in a NullPointerException, so I’ll use a simple example here
String result = test.getName().getTime().getNum().getAnswer();
Copy the code
In the above code, if we want to ensure that no exceptions are raised, we explicitly check each value before accessing it, that is, usingif else
Checking for null test equivalents can easily become tedious and difficult to maintain. To simplify this process, Google’s famous Guava project introduced the Optional class, which prevents code contamination by checking for null values and encourages programmers to write cleaner code. Optional is actually a container: it can hold values of type T, or just NULL. Optional provides a number of useful methods so that we don’t have to explicitly null-check.
Optional constructor
Option.of (obj), option.ofnullable (obj), and explicitly option.empty ()
- Optional.of(obj) : It requires that the obj passed in cannot be null, otherwise NullPointerException is declared.
- Optional.ofNullable(obj) : It constructs an Optional instance in an intelligent, tolerant way. If null is passed in, you get optional. empty(); if not, you call optional. of(obj).
- Option.empty () : Returns an empty Optional object
Optional common function
- Of: Creates an Optional value for non-null values. The of method creates the Optional class through the factory method. Note that the object cannot be passed as a null argument. If a null argument is passed in, a NullPointerException is thrown. So you don’t use it very often.
- OfNullable: Creates an Optional value for the specified value, or returns an empty Optional value if the specified value is null.
- IsPresent: Returns true if the value exists, false otherwise.
- IfPresent: Call consumer for the Optional instance if it has a value, otherwise nothing is done
- Get: Returns Optional if it has a value, otherwise throws NoSuchElementException. So I don’t use it very often.
- OrElse: returns a value if there is one, otherwise returns another value specified.
- OrElseGet: orElseGet is similar to the orElse method except that the default value is obtained. The orElse method takes the string passed as the default value, and the orElseGet method can accept the Supplier interface implementation to generate the default value
- OrElseThrow: Returns a value if there is one, otherwise throws an exception created by the Supplier interface.
- Filter: Returns Optional containing the value if it has a value and satisfies the assertion condition, otherwise returns empty Optional.
- Map: If there is a value, call the mapping function to get the return value. If the return value is not null, create an Optional containing the mapping return value as the map method return value, otherwise return empty Optional.
- FlatMap: If there is a value, execute the mapping function for it and return the value of Optional type, otherwise return empty Optional.
How do you use Optional
There are a few things to consider when using Optional to determine when and how to use it. The important point is that Optional is not Serializable. Therefore, it should not be used as a class field. If you need to serialize an object that contains Optional values, the Jackson library supports treating Optional as a normal object. That is, Jackson treats an empty object as null, while a valued object treats its value as the value of the corresponding field. This functionality is in the jackson-modules-Java8 project. Optional is used primarily as a return type, and once you get an instance of that type, you can get the value if it has a value, otherwise you can do some alternative behavior. The Optional class can combine this with streams or other methods that return Optional to build a smooth API. So let’s look at an example where we don’t write code with Optional
public String getName(User user){
if(user == null) {return "Unknown";
}else return user.name();
}
Copy the code
Next, let’s modify the code above using Optional. Let’s start with an example of Optional abuse that doesn’t achieve a smooth chain API, but instead gets complicated
public String getName(User user){
Optional<User> u = Optional.ofNullable(user);
if(! u.isPresent()){return "Unknown";
}else return u.get().name();
}
Copy the code
Not only is this rewriting not neat, but it still behaves the same as the first code. The isPresent method is used instead of user==null. This is not Optional, so let’s rewrite it again.
public String getName(User user){
return Optional.ofNullable(user)
.map(u -> u.name)
.orElse("Unknown");
}
Copy the code
This is the correct Optional pose. So in this line of thinking, we can safely make chain calls instead of layer by layer. Of course, we can further reduce the code through the getter method (provided that the User has a getter method), as follows
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");
Copy the code
Optional Best Practices
First of all, let’s take a look at some of the examples of Optional
- Avoid the use of
Optional.isPresent()
To check if the instance exists (mentioned in the example above), because this way andnull ! = obj
It doesn’t make a difference, so it doesn’t make sense. - Avoid the use of
Optional.get()
Method to obtain the instance object, as required before useOptional.isPresent()
To check if the instance exists, otherwise NoSuchElementException occurs. So usingOrElse (), orElseGet(), orElseThrow()
Get your results
OrElse (…) Is an eager calculation, which means something like the following:
Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
.map(this::printUserAndReturnUser)
.orElse(this::printVoidAndReturnUser)
Copy the code
Both methods are executed if the value is present, and only the last method is executed if the value is not. To handle these cases, we can use the orElseGet () method, which takes supplier as an argument and is lazy.
- Avoid using Optional as an attribute of a class or instance. Instead, wrap the return instance object in the return value.
- Avoid using Optional as an argument to methods for the same reason as 3.
- Do not assign NULL to Optional
- The only good place to use Optional is whenever the result is uncertain. In a sense, this is the only good place to use Optional. Our goal is to provide a limited mechanism for the return types of library methods, where there needs to be an explicit way to say “no result,” and the use of NULL for such methods can definitely lead to errors.
- Don’t be afraid to use Map and filter, there are some general development practices worth following called SLA-P: Single Layer of first uppercase Abstraction letters. Below is the code that needs to be refactored to refactored
The sample a
Dog dog = fetchSomeVaraible();
String dogString = dogToString(dog);
public String dogToString(Dog dog){
if(dog == null) {return "DOG'd name is : " + dog.getName();
} else {
return "CAT"; }}// Refactor the above code to the following code
Optional<Dog> dog = fetchDogIfExists();
String dogsName = dog
.map(this::convertToDog)
.orElseGet(this::convertToCat)
public void convertToDog(Dog dog){
return "DOG'd name is : " + dog.getName();
}
public void convertToCat(a){
return "CAT";
}
Copy the code
Example 2
Dog dog = fetchDog();
if(optionalDog ! =null && optionalDog.isBigDog()){
doBlaBlaBla(optionalDog);
}
// Refactor the above code to the following code
Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
.filter(Dog::isBigDog)
.ifPresent(this::doBlaBlaBla)
Copy the code
- Do not use Optional for chained methods. One thing to be aware of when using optional is the temptation of the chained method. When we link methods like the builder pattern, things can look pretty. But it doesn’t always equal more readability. So don’t do that, it’s not good for performance, it’s not good for readability. We should avoid null references whenever possible.
Optional
.ofNullable(someVariable)
.ifPresent(this::blablabla)
Copy the code
- Makes all expressions a single-line lambda. This is the more general rule that I think should apply to flows as well. But this article is about Optional. The important thing to remember with Optional is that the left-hand side of the equation is just as important as the right-hand side, so here’s an example
Optional
.ofNullable(someVariable)
.map(variable -> {
try{
return someREpozitory.findById(variable.getIdOfOtherObject());
} catch (IOException e){
LOGGER.error(e);
throw new RuntimeException(e);
}})
.filter(variable -> {
if(variable.getSomeField1() ! =null) {return true;
} else if(variable.getSomeField2() ! =null) {return false;
} else {
return true;
}
})
.map((variable -> {
try{
return jsonMapper.toJson(variable);
} catch (IOException e){
LOGGER.error(e);
throw new RuntimeException(e);
}}))
.map(String::trim)
.orElseThrow(() -> new RuntimeException("something went horribly wrong."))
Copy the code
The above verbose code block can be replaced with a method:
Optional
.ofNullable(someVariable)
.map(this::findOtherObject)
.filter(this::isThisOtherObjectStale)
.map(this::convertToJson)
.map(String::trim)
.orElseThrow(() -> new RuntimeException("something went horribly wrong."));
Copy the code